diff --git a/.codespell/ignore-words.txt b/.codespell/ignore-words.txt index 48bee0f30ba..2ae7d88d6ba 100644 --- a/.codespell/ignore-words.txt +++ b/.codespell/ignore-words.txt @@ -27,3 +27,5 @@ straightaway ftbs ftb curren +mabey +rsource diff --git a/.devcontainer/cortex-m-toolchain.sh b/.devcontainer/cortex-m-toolchain.sh index 56be006544f..7fe5135ebc2 100755 --- a/.devcontainer/cortex-m-toolchain.sh +++ b/.devcontainer/cortex-m-toolchain.sh @@ -14,10 +14,10 @@ echo -e "[cortex-m-toolchain.sh] downloading and installing gcc-arm-non-eabi too cd /workspaces wget -qO gcc-arm-none-eabi.tar.xz \ - https://developer.arm.com/-/media/Files/downloads/gnu/15.2.rel1/binrel/arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi.tar.xz + https://gitlab.arm.com/api/v4/projects/tooling%2Fgnu-toolchains-for-arm/packages/generic/gnu-toolchain/15.3.rel1/arm-gnu-toolchain-15.3.rel1-x86_64-arm-none-eabi.tar.xz tar -xJf gcc-arm-none-eabi.tar.xz -ln -s arm-gnu-toolchain-15.2.rel1-x86_64-arm-none-eabi gcc-arm-none-eabi +ln -s arm-gnu-toolchain-15.3.rel1-x86_64-arm-none-eabi gcc-arm-none-eabi rm -f gcc-arm-none-eabi.tar.xz echo -e "[cortex-m-toolchain.sh] update PATH in environment" diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 195e1e06eea..97b5e558f40 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,3 +1,6 @@ +# all: Apply code formatting to new paths. +48c7daaa3d7068c127c5bc395ffa72029ab9bba1 + # all: Prune trailing whitespace. dda9b9c6da5d3c31fa8769e581a753e95a270803 diff --git a/.gitattributes b/.gitattributes index d226cebf5d8..890c9cead6c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,12 +13,14 @@ # CIRCUITPY-CHANGE: add some more binary types. # These are binary so should never be modified by git. *.a binary +*.FLM binary *.ico binary *.png binary *.jpg binary *.dxf binary *.mpy binary *.der binary +*.bin binary *.deb binary *.zip binary *.pdf binary diff --git a/.github/actions/deps/external/action.yml b/.github/actions/deps/external/action.yml index 326bc8275c1..9f6dbf222a4 100644 --- a/.github/actions/deps/external/action.yml +++ b/.github/actions/deps/external/action.yml @@ -21,12 +21,11 @@ runs: - name: Get arm toolchain if: >- inputs.port != 'none' && - inputs.port != 'litex' && inputs.port != 'espressif' && inputs.port != 'zephyr-cp' uses: carlosperate/arm-none-eabi-gcc-action@v1 with: - release: '15.2.Rel1' + release: '15.3.Rel1' # espressif - name: Install IDF tools diff --git a/.github/actions/deps/ports/action.yml b/.github/actions/deps/ports/action.yml index cd3b4c80fe2..77e04a45e16 100644 --- a/.github/actions/deps/ports/action.yml +++ b/.github/actions/deps/ports/action.yml @@ -11,18 +11,10 @@ inputs: runs: using: composite steps: - - name: Set up broadcom - if: inputs.port == 'broadcom' - uses: ./.github/actions/deps/ports/broadcom - - name: Set up espressif if: inputs.port == 'espressif' uses: ./.github/actions/deps/ports/espressif - - name: Set up litex - if: inputs.port == 'litex' - uses: ./.github/actions/deps/ports/litex - - name: Set up nordic if: inputs.port == 'nordic' uses: ./.github/actions/deps/ports/nordic diff --git a/.github/actions/deps/ports/broadcom/action.yml b/.github/actions/deps/ports/broadcom/action.yml deleted file mode 100644 index 8ee85e35138..00000000000 --- a/.github/actions/deps/ports/broadcom/action.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Fetch broadcom port deps - -runs: - using: composite - steps: - - name: Get broadcom toolchain - run: | - wget --no-verbose https://adafruit-circuit-python.s3.amazonaws.com/arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-elf.tar.xz - sudo tar -C /usr --strip-components=1 -xaf arm-gnu-toolchain-15.2.rel1-x86_64-aarch64-none-elf.tar.xz - sudo apt-get update - sudo apt-get install -y mtools - shell: bash - - name: Install mkfs.fat - run: | - wget https://github.com/dosfstools/dosfstools/releases/download/v4.2/dosfstools-4.2.tar.gz - tar -xaf dosfstools-4.2.tar.gz - cd dosfstools-4.2 - ./configure - make -j$(nproc) - cd src - echo >> $GITHUB_PATH $(pwd) - shell: bash diff --git a/.github/actions/deps/ports/litex/action.yml b/.github/actions/deps/ports/litex/action.yml deleted file mode 100644 index a89a2225052..00000000000 --- a/.github/actions/deps/ports/litex/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Fetch litex port deps - -runs: - using: composite - steps: - - name: Get litex toolchain - run: | - wget https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.3.0-2019.08.0-x86_64-linux-centos6.tar.gz - sudo tar -C /usr --strip-components=1 -xaf riscv64-unknown-elf-gcc-8.3.0-2019.08.0-x86_64-linux-centos6.tar.gz - shell: bash diff --git a/.github/actions/deps/ports/zephyr-cp/action.yml b/.github/actions/deps/ports/zephyr-cp/action.yml index bd0af847773..c63d9397e47 100644 --- a/.github/actions/deps/ports/zephyr-cp/action.yml +++ b/.github/actions/deps/ports/zephyr-cp/action.yml @@ -33,6 +33,12 @@ runs: app-path: zephyr-config base-path: ports/zephyr-cp toolchains: arm-zephyr-eabi + # The action's ccache stores a new ~150 MB entry per board job under one key + # shared by every board, and each job restores whichever entry was saved last. + # Measured across all 30 boards: hits on 5 boards by luck, none on the other 25, + # and no build-time difference either way. The entries were filling the + # repository's 10 GB Actions cache and evicting the SDK and other caches. + enable-ccache: false - name: Export cmake info run: west zephyr-export shell: bash diff --git a/.github/workflows/build-boards.yml b/.github/workflows/build-boards.yml index 6243d50135b..016a2f2f32e 100644 --- a/.github/workflows/build-boards.yml +++ b/.github/workflows/build-boards.yml @@ -24,7 +24,7 @@ jobs: env: CP_VERSION: ${{ inputs.cp-version }} strategy: - fail-fast: false + fail-fast: ${{ github.event_name == 'merge_group' }} matrix: board: ${{ fromJSON(inputs.boards) }} steps: @@ -94,6 +94,17 @@ jobs: name: ${{ matrix.board }} path: bin/${{ matrix.board }} + - name: Upload firmware sizes + # "zz-" sorts the size records after every board in the run's artifact list. + if: ${{ always() }} + uses: actions/upload-artifact@v7 + with: + name: zz-sizes-${{ matrix.board }} + path: sizes/${{ matrix.board }}.json + if-no-files-found: ignore + # build-ci merges these into the 0-sizes reports; delete them after one day. + retention-days: 1 + - name: Upload to S3 uses: ./.github/actions/upload_aws with: diff --git a/.github/workflows/build-mpy-cross.yml b/.github/workflows/build-mpy-cross.yml index d6698f96a56..dedf9154b8d 100644 --- a/.github/workflows/build-mpy-cross.yml +++ b/.github/workflows/build-mpy-cross.yml @@ -16,7 +16,7 @@ jobs: build: runs-on: ubuntu-24.04 strategy: - fail-fast: false + fail-fast: ${{ github.event_name == 'merge_group' }} matrix: mpy-cross: ["static", "static-aarch64", "static-mingw", "static-raspbian"] env: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5e35dc453c..f0f57a74243 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,11 +10,13 @@ on: - main - '*.x' pull_request: + merge_group: + types: [checks_requested] release: types: [published] concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref || github.run_id }} cancel-in-progress: true jobs: @@ -69,15 +71,15 @@ jobs: GITHUB_TOKEN: ${{ github.token }} EXCLUDE_COMMIT: ${{ github.event.pull_request.head.sha }} - name: Set head sha (pull) - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' || github.event_name == 'merge_group' env: - HEAD_SHA: ${{ github.event.pull_request.head.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} run: echo "HEAD_SHA=$HEAD_SHA" >> $GITHUB_ENV - name: Set base sha (pull) - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' || github.event_name == 'merge_group' run: git cat-file -e $SHA && echo "BASE_SHA=$SHA" >> $GITHUB_ENV || true env: - SHA: ${{ steps.get-last-commit-with-checks.outputs.commit_sha || github.event.pull_request.base.sha }} + SHA: ${{ steps.get-last-commit-with-checks.outputs.commit_sha || github.event.pull_request.base.sha || github.event.merge_group.base_sha }} - name: Set head sha (push) if: github.event_name == 'push' env: @@ -227,10 +229,107 @@ jobs: uses: ./.github/workflows/build-boards.yml secrets: inherit strategy: - fail-fast: false + fail-fast: ${{ github.event_name == 'merge_group' }} + max-parallel: 55 matrix: port: ${{ fromJSON(needs.scheduler.outputs.ports).ports }} with: boards: ${{ toJSON(fromJSON(needs.scheduler.outputs.ports)[matrix.port]) }} cp-version: ${{ needs.scheduler.outputs.cp-version }} port: ${{ fromJSON(needs.scheduler.outputs.ports).split_ports[matrix.port] || matrix.port }} + + build-ci: + # Final gate for ruleset checks: always runs so the required check is + # reported, and fails if any job (or matrix leg) failed or was cancelled. + needs: [scheduler, tests, zephyr-tests, mpy-cross, mpy-cross-mac, docs, ports] + if: ${{ always() }} + runs-on: ubuntu-24.04 + env: + CP_VERSION: ${{ needs.scheduler.outputs.cp-version }} + steps: + # Firmware size report. Each board job uploads its sizes/.json as zz-sizes-; + # merge them into one table and a job summary. This is reporting only, so none of it + # may fail the gate: every step continues on error. + - name: Set up repository + continue-on-error: true + uses: actions/checkout@v6 + with: + submodules: false + show-progress: false + fetch-depth: 1 + persist-credentials: false + + # actions/download-artifact matches a pattern against a listing of the run's + # artifacts that stops at 1000, and a full build has more. The script fetches each + # board's record by exact name instead, from the board list the scheduler produced. + # It runs from github-script because only action steps get the artifact API token. + - name: Install the artifact client + continue-on-error: true + run: npm install --no-save --no-package-lock --no-audit --no-fund @actions/artifact@6 + + - name: Download firmware sizes + continue-on-error: true + uses: actions/github-script@v9 + env: + BOARDS: ${{ needs.scheduler.outputs.ports }} + with: + script: | + const { pathToFileURL } = require("node:url"); + const sizes = await import(pathToFileURL(`${process.env.GITHUB_WORKSPACE}/tools/ci_download_sizes.mjs`)); + await sizes.downloadSizes({ + boards: sizes.boardsFromSchedule(JSON.parse(process.env.BOARDS || "{}")), + outDir: "sizes", + info: core.info, + warning: core.warning, + }); + + - name: Summarize firmware sizes + continue-on-error: true + run: python3 tools/ci_firmware_sizes.py sizes sizes-report + + # Uploaded unzipped, one file per artifact, so each opens directly; with + # `archive: false` the file name is the artifact name. + - name: Upload firmware size report (HTML) + continue-on-error: true + uses: actions/upload-artifact@v7 + with: + path: sizes-report/0-sizes.html + archive: false + if-no-files-found: ignore + + - name: Upload firmware size report (JSON) + continue-on-error: true + uses: actions/upload-artifact@v7 + with: + path: sizes-report/0-sizes.json + archive: false + if-no-files-found: ignore + + # The action itself only uploads on pushes to main and release branches and on + # published releases. The S3 names carry the version, so the directory keeps a history. + - name: Upload firmware size report to S3 (HTML) + continue-on-error: true + uses: ./.github/actions/upload_aws + with: + source: sizes-report/0-sizes.html + destination: 0-sizes/adafruit-circuitpython-sizes-${{ env.CP_VERSION }}.html + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Upload firmware size report to S3 (JSON) + continue-on-error: true + uses: ./.github/actions/upload_aws + with: + source: sizes-report/0-sizes.json + destination: 0-sizes/adafruit-circuitpython-sizes-${{ env.CP_VERSION }}.json + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Check build results + run: | + echo "Job results: ${{ toJSON(needs.*.result) }}" + if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]] || [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then + echo "One or more jobs failed or were cancelled" + exit 1 + fi + echo "All build jobs succeeded" diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 21ae984e468..46961aca41f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -6,10 +6,14 @@ name: pre-commit on: push: + branches-ignore: + - 'gh-readonly-queue/**' pull_request: + merge_group: + types: [checks_requested] concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref || github.run_id }} cancel-in-progress: true jobs: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e3258907f9d..db4f5b0e316 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -11,7 +11,7 @@ jobs: run: runs-on: ubuntu-24.04 strategy: - fail-fast: false + fail-fast: ${{ github.event_name == 'merge_group' }} matrix: test: [all, mpy, native, native_mpy] env: diff --git a/.github/workflows/run-zephyr-tests.yml b/.github/workflows/run-zephyr-tests.yml index e99da28fe42..d9518c2d834 100644 --- a/.github/workflows/run-zephyr-tests.yml +++ b/.github/workflows/run-zephyr-tests.yml @@ -12,20 +12,20 @@ jobs: runs-on: ubuntu-24.04 name: ${{ matrix.shard }} strategy: - fail-fast: false + fail-fast: ${{ github.event_name == 'merge_group' }} # One job per board family, so the bsim suites, which take the longest, run # side by side instead of after each other. matrix: include: - shard: native_sim - boards: native_native_sim native_native_sim_asan - pytest_args: tests/ --ignore=tests/bsim + boards: native_native_sim native_native_sim_asan native_native_sim_lfs + pytest_args: tests/ --ignore=tests/bsim -n $(nproc) --dist loadfile - shard: nrf5340bsim boards: native_nrf5340bsim - pytest_args: tests/bsim -k native_nrf5340bsim + pytest_args: tests/bsim -k native_nrf5340bsim -n $(nproc) --dist loadfile - shard: nrf54lm20bsim boards: native_nrf54lm20bsim - pytest_args: tests/bsim -k native_nrf54lm20bsim + pytest_args: tests/bsim -k native_nrf54lm20bsim -n $(nproc) --dist loadfile env: CP_VERSION: ${{ inputs.cp-version }} steps: @@ -55,4 +55,4 @@ jobs: run: make -j $(nproc) everything working-directory: ports/zephyr-cp/tools/bsim - name: Run Zephyr tests - run: make -C ports/zephyr-cp test TEST_BOARDS="${{ matrix.boards }}" PYTEST_ARGS="${{ matrix.pytest_args }}" + run: make -C ports/zephyr-cp -j$(nproc) test TEST_BOARDS="${{ matrix.boards }}" PYTEST_ARGS="${{ matrix.pytest_args }}" diff --git a/.github/workflows/zephyr-autogen-pr.yml b/.github/workflows/zephyr-autogen-pr.yml new file mode 100644 index 00000000000..25a0e9e686e --- /dev/null +++ b/.github/workflows/zephyr-autogen-pr.yml @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) +# +# SPDX-License-Identifier: MIT + +# Build every zephyr-cp board and open a PR when the committed autogenerated +# files (autogen_board_info.toml) changed as a result. + +name: Update Zephyr autogenerated files + +on: + push: + branches: + - main + # Test branches: push a branch matching this pattern to run manually. + - 'zephyr-autogen-*' + workflow_dispatch: + +# The default GITHUB_TOKEN needs elevated permissions to push the autogen +# branch and open the PR. +permissions: + contents: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + autogen: + name: Build all zephyr-cp boards + runs-on: ubuntu-24.04 + timeout-minutes: 360 + steps: + - name: Set up repository + uses: actions/checkout@v6 + with: + submodules: false + show-progress: false + fetch-depth: 1 + - name: Set up python + uses: actions/setup-python@v6 + with: + python-version: 3.13 + - name: Set up Zephyr + uses: ./.github/actions/deps/ports/zephyr-cp + with: + # native_sim needs the 32-bit SDL2 packages + sdl: true + - name: Set up submodules + uses: ./.github/actions/deps/submodules + with: + target: zephyr-cp + version: true + - name: Set up external + uses: ./.github/actions/deps/external + - name: Build all boards + run: make -C ports/zephyr-cp -j$(nproc) all + - name: Print build logs + if: always() + run: | + shopt -s nullglob + for log in ports/zephyr-cp/build-logs/*.log; do + echo "::group::$(basename "$log" .log)" + cat "$log" + echo "::endgroup::" + done + - name: Check for autogenerated changes + id: diff + run: | + if [ -n "$(git status --porcelain -- 'ports/zephyr-cp/boards/**/autogen_board_info.toml')" ]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + git status --short -- 'ports/zephyr-cp/boards/**/autogen_board_info.toml' + fi + - name: Find existing open autogen PR + id: pr + env: + GH_TOKEN: ${{ github.token }} + run: | + pr=$(gh pr list --state open --head zephyr-autogen-board-info --json number --jq '.[0].number // empty') + echo "number=$pr" >> "$GITHUB_OUTPUT" + if [ -n "$pr" ]; then + echo "Found existing open autogen PR #$pr; will push to it." + fi + - name: Delete stale autogen branch + # A new pull request cannot be created from a head branch that already + # had one. If the previous autogen PR was closed without merging, the + # branch lingers on; delete it so create-pull-request can start over. + if: steps.diff.outputs.changed == 'true' && steps.pr.outputs.number == '' + run: | + if git ls-remote --heads origin zephyr-autogen-board-info | grep -q zephyr-autogen-board-info; then + git push origin --delete zephyr-autogen-board-info + fi + - name: Create PR + if: steps.diff.outputs.changed == 'true' + uses: peter-evans/create-pull-request@v8 + with: + add-paths: ports/zephyr-cp/boards/**/autogen_board_info.toml + commit-message: Regenerate zephyr-cp autogen_board_info.toml files + title: Regenerate zephyr-cp autogenerated board info + # create-pull-request pushes to the branch of an existing open PR, + # so a still-open autogen PR is updated in place instead of spawning + # a second one. + body: | + The `autogen_board_info.toml` files changed after building all zephyr-cp + boards on `main`. Created by the "Update Zephyr autogenerated files" + workflow. + + Based on commit: ${{ github.sha }} + Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + branch: zephyr-autogen-board-info + delete-branch: true diff --git a/.gitignore b/.gitignore index ca6872387dd..ec4811a6e30 100644 --- a/.gitignore +++ b/.gitignore @@ -36,10 +36,13 @@ dist/ ###################### build/ bin/ +sizes/ +node_modules/ circuitpython-stubs/ test-stubs/ build-*/ docs/genrst/ +.mpy_ld_cache-*/ # Test failure outputs and intermediate artifacts ###################### diff --git a/.gitmodules b/.gitmodules index 0dee3eb5cd4..0fcda5904a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -147,7 +147,7 @@ [submodule "ports/espressif/esp-protocols"] path = ports/espressif/esp-protocols url = https://github.com/adafruit/esp-protocols.git - branch = circuitpython + branch = circuitpython2026 [submodule "ports/espressif/esp-camera"] path = ports/espressif/esp-camera url = https://github.com/adafruit/esp32-camera.git @@ -190,11 +190,6 @@ [submodule "frozen/Adafruit_CircuitPython_APDS9960"] path = frozen/Adafruit_CircuitPython_APDS9960 url = https://github.com/adafruit/Adafruit_CircuitPython_APDS9960 -[submodule "rpi-firmware"] - path = ports/broadcom/firmware - url = https://github.com/raspberrypi/rpi-firmware.git - branch = master - shallow = true [submodule "lib/adafruit_floppy"] path = lib/adafruit_floppy url = https://github.com/adafruit/Adafruit_Floppy @@ -323,10 +318,6 @@ [submodule "frozen/Adafruit_CircuitPython_SSD1680"] path = frozen/Adafruit_CircuitPython_SSD1680 url = https://github.com/adafruit/Adafruit_CircuitPython_SSD1680 -[submodule "ports/broadcom/peripherals"] - path = ports/broadcom/peripherals - url = https://github.com/adafruit/broadcom-peripherals.git - branch = main-build [submodule "ports/silabs/gecko_sdk"] path = ports/silabs/gecko_sdk url = https://github.com/SiliconLabs/gecko_sdk.git @@ -346,7 +337,7 @@ url = https://github.com/adafruit/Adafruit_CircuitPython_Wave.git [submodule "ports/raspberrypi/lib/Pico-PIO-USB"] path = ports/raspberrypi/lib/Pico-PIO-USB - url = https://github.com/sekigon-gonnoc/Pico-PIO-USB.git + url = https://github.com/adafruit/Pico-PIO-USB.git branch = main [submodule "lib/micropython-lib"] path = lib/micropython-lib diff --git a/BUILDING.md b/BUILDING.md index 6778965bed0..6986dbb7224 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -25,13 +25,20 @@ Please ensure you set up your build environment appropriately, as per the guide. This project has a bunch of git submodules. You will need to update them regularly. -In the root folder of the CircuitPython repository, execute the following: +Fetch only what the board you are building needs. From the root folder of the CircuitPython +repository: - make fetch-all-submodules + make -C ports/raspberrypi fetch-board-submodules BOARD=raspberry_pi_pico + +Or everything one port needs, which is what you want if you build several boards from it: -Or, in the ports directory for the particular port you are building, do: + make -C ports/raspberrypi fetch-port-submodules - make fetch-port-submodules +There is also a target that fetches the submodules of every port at once. That is a few gigabytes +against a few hundred megabytes for one board, so use it only if you really are building for +more than one port: + + make fetch-all-submodules ### Required Python Packages diff --git a/Makefile b/Makefile index 3fc38581f9b..e7150281352 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(BASEOPTS) # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(BASEOPTS) -TRANSLATE_SOURCES = extmod lib main.c ports/atmel-samd ports/analog ports/cxd56 ports/espressif ports/mimxrt10xx ports/nordic ports/raspberrypi ports/renode ports/stm ports/zephyr-cp py shared-bindings shared-module supervisor +TRANSLATE_SOURCES = extmod lib main.c ports/atmel-samd ports/analog ports/cxd56 ports/espressif ports/mimxrt10xx ports/nordic ports/raspberrypi ports/stm ports/zephyr-cp py shared-bindings shared-module supervisor # Paths to exclude from TRANSLATE_SOURCES # Each must be preceded by "-path"; if any wildcards, enclose in quotes. # Separate by "-o" (Find's "or" operand) @@ -93,8 +93,13 @@ help: @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " fetch-all-submodules to fetch submodules for all ports" + @echo " fetch-all-submodules to fetch submodules for all ports (several GB)" @echo " remove-all-submodules remove all submodules, including files and .git/ data" + @echo "" + @echo "To build one board you only need its own submodules, which is a much smaller" + @echo "download. From a port directory:" + @echo " make fetch-board-submodules BOARD= submodules for one board" + @echo " make fetch-port-submodules submodules for every board of that port" clean: rm -rf $(BUILDDIR)/* @@ -297,7 +302,7 @@ update-frozen-libraries: @echo "Updating all frozen libraries to latest tagged version." cd frozen; for library in *; do cd $$library; ../../tools/git-checkout-latest-tag.sh; cd ..; done -one-of-each: samd21 litex mimxrt10xx nordic stm +one-of-each: samd21 mimxrt10xx nordic stm analog: $(MAKE) -C ports/analog/ BOARD=apard32690 @@ -311,9 +316,6 @@ samd51: espressif: $(MAKE) -C ports/espressif BOARD=espressif_saola_1_wroom -litex: - $(MAKE) -C ports/litex BOARD=fomu - mimxrt10xx: $(MAKE) -C ports/mimxrt10xx BOARD=feather_mimxrt1011 @@ -323,7 +325,7 @@ nordic: stm: $(MAKE) -C ports/stm BOARD=feather_stm32f405_express -clean-one-of-each: clean-samd21 clean-samd51 clean-espressif clean-litex clean-mimxrt10xx clean-nordic clean-stm +clean-one-of-each: clean-samd21 clean-samd51 clean-espressif clean-mimxrt10xx clean-nordic clean-stm clean-samd21: $(MAKE) -C ports/atmel-samd BOARD=trinket_m0 clean @@ -334,9 +336,6 @@ clean-samd51: clean-espressif: $(MAKE) -C ports/espressif BOARD=espressif_saola_1_wroom clean -clean-litex: - $(MAKE) -C ports/litex BOARD=fomu clean - clean-mimxrt10xx: $(MAKE) -C ports/mimxrt10xx BOARD=feather_mimxrt1011 clean diff --git a/README.rst b/README.rst index 89c7d9229d8..7cb5ccc27f8 100644 --- a/README.rst +++ b/README.rst @@ -226,7 +226,7 @@ Ports Ports include the code unique to a microcontroller line. -The following ports are available: ``atmel-samd``, ``cxd56``, ``espressif``, ``litex``, ``mimxrt10xx``, ``nordic``, ``raspberrypi``, ``renode``, ``silabs`` (``efr32``), ``stm``, ``unix``, and ``zephyr-cp``. +The following ports are available: ``atmel-samd``, ``cxd56``, ``espressif``, ``mimxrt10xx``, ``nordic``, ``raspberrypi``, ``silabs`` (``efr32``), ``stm``, ``unix``, and ``zephyr-cp``. However, not all ports are fully functional. Some have limited functionality and known serious bugs. For details, refer to the **Port status** section in the `latest release `__ notes. diff --git a/conf.py b/conf.py index a584031b01b..379ac442f19 100644 --- a/conf.py +++ b/conf.py @@ -83,6 +83,18 @@ "sphinx_search.extension", "rstjinja", "myst_parser", + "llms_txt", +] + +# llms.txt: an index of every page for coding agents (docs/llms_txt.py) +llms_txt_description = ( + "Adafruit CircuitPython - Python for microcontrollers. " + "Each page is also served as Markdown with the Accept: text/markdown header." +) +llms_txt_exclude = [ + "docs/porting", + "docs/pdf", + "shared-bindings/support_matrix", ] # Add any paths that contain templates here, relative to this directory. @@ -331,6 +343,7 @@ def autoapi_prepare_jinja_env(jinja_env): \hbadness=99999 \hfuzz=20pt \usepackage{pdflscape} +\DeclareUnicodeCharacter{FFFD}{?} """, } diff --git a/devices/ble_hci/common-hal/_bleio/Adapter.c b/devices/ble_hci/common-hal/_bleio/Adapter.c index 005dd0aeb03..cf482ee6418 100644 --- a/devices/ble_hci/common-hal/_bleio/Adapter.c +++ b/devices/ble_hci/common-hal/_bleio/Adapter.c @@ -160,6 +160,13 @@ static void check_enabled(bleio_adapter_obj_t *adapter) { } } +// A controller that supports extended advertising refuses legacy advertising +// commands (Command Disallowed) once any extended command has been sent, so +// use the extended command set exclusively on such controllers. +static bool adapter_uses_extended_advertising(bleio_adapter_obj_t *adapter) { + return BT_FEAT_LE_EXT_ADV(adapter->features); +} + // static bool adapter_on_ble_evt(ble_evt_t *ble_evt, void *self_in) { // bleio_adapter_obj_t *self = (bleio_adapter_obj_t*)self_in; @@ -296,17 +303,6 @@ static void bleio_adapter_hci_init(bleio_adapter_obj_t *self) { self->max_acl_buffer_len = acl_max_len; self->max_acl_num_buffers = acl_max_num; } - - // Get max advertising length if extended advertising is supported. - if (BT_FEAT_LE_EXT_ADV(self->features)) { - uint16_t max_adv_data_len; - if (hci_le_read_maximum_advertising_data_length(&max_adv_data_len) != HCI_OK) { - mp_raise_bleio_BluetoothError(MP_ERROR_TEXT("Could not get max advertising length")); - } - self->max_adv_data_len = max_adv_data_len; - } else { - self->max_adv_data_len = MAX_ADVERTISEMENT_SIZE; - } } void common_hal_bleio_adapter_construct_hci_uart(bleio_adapter_obj_t *self, busio_uart_obj_t *uart, digitalio_digitalinout_obj_t *rts, digitalio_digitalinout_obj_t *cts) { @@ -343,7 +339,6 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable // Enabling or disabling: stop any current activity; reset to known state. hci_reset(); self->now_advertising = false; - self->extended_advertising = false; self->circuitpython_advertising = false; self->advertising_timeout_msecs = 0; @@ -633,19 +628,17 @@ uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, memcpy(&peer_addr.a.val, directed_to->bytes, sizeof(peer_addr.a.val)); } - bool extended = - advertising_data_len > self->max_adv_data_len || scan_response_data_len > self->max_adv_data_len; - - if (extended) { - if (!BT_FEAT_LE_EXT_ADV(self->features)) { - mp_raise_bleio_BluetoothError(MP_ERROR_TEXT("Data length needs extended advertising, but this adapter does not support it")); - } - - uint16_t props = 0; + if (adapter_uses_extended_advertising(self)) { + // Use legacy PDUs (ADV_IND etc.) so that 4.x centrals still see us and the + // 31-byte data limit applies unchanged; only the HCI command set is extended. + uint16_t props = BT_HCI_LE_ADV_PROP_LEGACY; if (connectable) { - props |= BT_HCI_LE_ADV_PROP_CONN; - } - if (scan_response_data_len > 0) { + if (directed_to) { + props |= BT_HCI_LE_ADV_PROP_CONN | BT_HCI_LE_ADV_PROP_DIRECT; + } else { + props |= BT_HCI_LE_ADV_PROP_CONN | BT_HCI_LE_ADV_PROP_SCAN; + } + } else if (scan_response_data_len > 0) { props |= BT_HCI_LE_ADV_PROP_SCAN; } @@ -660,32 +653,53 @@ uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, interval_units, // max interval 0b111, // channel map: channels 37, 38, 39 anonymous ? BT_ADDR_LE_RANDOM : BT_ADDR_LE_PUBLIC, - &peer_addr, // peer_addr, + &peer_addr, 0x00, // filter policy: no filter - DEFAULT_TX_POWER, - BT_HCI_LE_EXT_SCAN_PHY_1M, // Secondary PHY to use - 0x00, // AUX_ADV_IND shall be sent prior to next adv event - BT_HCI_LE_EXT_SCAN_PHY_1M, // Secondary PHY to use - 0x00, // Advertising SID - 0x00 // Scan req notify disable + BT_HCI_LE_ADV_TX_POWER_NO_PREF, + BT_HCI_LE_EXT_SCAN_PHY_1M, // primary PHY + 0x00, // secondary max skip (unused with legacy PDUs) + BT_HCI_LE_EXT_SCAN_PHY_1M, // secondary PHY (unused with legacy PDUs) + 0x00, // advertising SID + 0x00 // scan request notification disabled )); - // We can use the duration mechanism provided, instead of our own. - self->advertising_timeout_msecs = 0; + hci_check_error( + hci_le_set_extended_advertising_data( + 0, // handle + BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA, + BT_HCI_LE_EXT_ADV_FRAG_DISABLED, + advertising_data_len, + (uint8_t *)advertising_data)); + + // A scannable set must have scan response data defined before it is enabled, + // even if that data is empty. + if (props & BT_HCI_LE_ADV_PROP_SCAN) { + hci_check_error( + hci_le_set_extended_scan_response_data( + 0, // handle + BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA, + BT_HCI_LE_EXT_ADV_FRAG_DISABLED, + scan_response_data_len, + (uint8_t *)scan_response_data)); + } + + // Use our own timeout rather than the controller's duration, so that we don't + // depend on receiving the LE Advertising Set Terminated event. + self->advertising_timeout_msecs = timeout * 1000; + self->advertising_start_ticks = supervisor_ticks_ms64(); + // Duration 0 and max events 0 mean "advertise until disabled by the host". uint8_t handle[1] = { 0 }; - uint16_t duration_10msec[1] = { timeout * 100 }; + uint16_t duration_forever[1] = { 0 }; uint8_t max_ext_adv_evts[1] = { 0 }; hci_check_error( hci_le_set_extended_advertising_enable( BT_HCI_LE_ADV_ENABLE, 1, // one advertising set. handle, - duration_10msec, + duration_forever, max_ext_adv_evts )); - - self->extended_advertising = true; } else { // Legacy advertising (not extended). @@ -732,7 +746,6 @@ uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, // Start advertising. hci_check_error(hci_le_set_advertising_enable(BT_HCI_LE_ADV_ENABLE)); - self->extended_advertising = false; } // end legacy advertising setup vm_used_ble = true; @@ -790,10 +803,18 @@ void common_hal_bleio_adapter_stop_advertising(bleio_adapter_obj_t *self) { check_enabled(self); self->now_advertising = false; - self->extended_advertising = false; self->circuitpython_advertising = false; - int result = hci_le_set_advertising_enable(BT_HCI_LE_ADV_DISABLE); + int result; + if (adapter_uses_extended_advertising(self)) { + // Duration and max events are ignored when disabling. + uint8_t handle[1] = { 0 }; + uint16_t duration_forever[1] = { 0 }; + uint8_t max_ext_adv_evts[1] = { 0 }; + result = hci_le_set_extended_advertising_enable(BT_HCI_LE_ADV_DISABLE, 1, handle, duration_forever, max_ext_adv_evts); + } else { + result = hci_le_set_advertising_enable(BT_HCI_LE_ADV_DISABLE); + } // OK if we're already stopped. There seems to be an ESP32 HCI bug: // If advertising is already off, then LE_SET_ADV_ENABLE does not return a response. if (result != HCI_RESPONSE_TIMEOUT) { @@ -807,7 +828,6 @@ void common_hal_bleio_adapter_stop_advertising(bleio_adapter_obj_t *self) { // Don't ask the adapter to stop. void bleio_adapter_advertising_was_stopped(bleio_adapter_obj_t *self) { self->now_advertising = false; - self->extended_advertising = false; self->circuitpython_advertising = false; } diff --git a/devices/ble_hci/common-hal/_bleio/Adapter.h b/devices/ble_hci/common-hal/_bleio/Adapter.h index c018c676171..ee7672649dd 100644 --- a/devices/ble_hci/common-hal/_bleio/Adapter.h +++ b/devices/ble_hci/common-hal/_bleio/Adapter.h @@ -34,7 +34,6 @@ typedef struct _bleio_adapter_obj_t { digitalio_digitalinout_obj_t *cts_digitalinout; bool allocated; // True when in use. bool now_advertising; - bool extended_advertising; bool circuitpython_advertising; bool enabled; @@ -56,7 +55,6 @@ typedef struct _bleio_adapter_obj_t { uint16_t max_acl_buffer_len; uint16_t max_acl_num_buffers; - uint16_t max_adv_data_len; uint8_t features[8]; // Supported BLE features. // All the local attributes for this device. The index into the list diff --git a/devices/ble_hci/common-hal/_bleio/Connection.c b/devices/ble_hci/common-hal/_bleio/Connection.c index 30ed0af575c..028b68796b5 100644 --- a/devices/ble_hci/common-hal/_bleio/Connection.c +++ b/devices/ble_hci/common-hal/_bleio/Connection.c @@ -323,6 +323,18 @@ bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { return self->connection->conn_handle != BLE_CONN_HANDLE_INVALID; } +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + return false; +} + +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { + return mp_const_none; +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + mp_raise_NotImplementedError(NULL); +} + void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self) { hci_disconnect(self->conn_handle); } diff --git a/devices/ble_hci/common-hal/_bleio/hci.c b/devices/ble_hci/common-hal/_bleio/hci.c index 126f05fbae2..a5d63e9015c 100644 --- a/devices/ble_hci/common-hal/_bleio/hci.c +++ b/devices/ble_hci/common-hal/_bleio/hci.c @@ -489,7 +489,9 @@ hci_result_t hci_send_acl_pkt(uint16_t handle, uint8_t cid, uint16_t data_len, u acl_data_t *acl_data = (acl_data_t *)acl_pkt->data; acl_pkt->pkt_type = H4_ACL; acl_pkt->handle = handle; - acl_pkt->pb = ACL_DATA_PB_FIRST_FLUSH; + // Host-to-controller LE data must use the non-flushable start flag; NimBLE + // controllers (ESP32-C6) silently discard packets with ACL_DATA_PB_FIRST_FLUSH. + acl_pkt->pb = ACL_DATA_PB_FIRST_NON_FLUSH; acl_pkt->bc = 0; acl_pkt->data_len = (uint16_t)(sizeof(acl_data_t) + data_len); acl_data->acl_data_len = data_len; @@ -608,7 +610,9 @@ hci_result_t hci_le_set_extended_advertising_parameters(uint8_t handle, uint16_t .prim_channel_map = prim_channel_map, .own_addr_type = own_addr_type, // .peer_addr set below. + .filter_policy = filter_policy, .tx_power = tx_power, + .prim_adv_phy = prim_adv_phy, .sec_adv_max_skip = sec_adv_max_skip, .sec_adv_phy = sec_adv_phy, .sid = sid, @@ -619,6 +623,7 @@ hci_result_t hci_le_set_extended_advertising_parameters(uint8_t handle, uint16_t sizeof_field(struct bt_hci_cp_le_set_ext_adv_param, prim_min_interval)); memcpy(params.prim_max_interval, (void *)&prim_max_interval, sizeof_field(struct bt_hci_cp_le_set_ext_adv_param, prim_max_interval)); + params.peer_addr.type = peer_addr->type; memcpy(params.peer_addr.a.val, peer_addr->a.val, sizeof_field(bt_addr_le_t, a.val)); return send_command(BT_HCI_OP_LE_SET_EXT_ADV_PARAM, sizeof(params), ¶ms); } @@ -672,6 +677,18 @@ hci_result_t hci_le_set_extended_advertising_data(uint8_t handle, uint8_t op, ui return send_command(BT_HCI_OP_LE_SET_EXT_ADV_DATA, sizeof(params) - (max_len - valid_len), ¶ms); } +hci_result_t hci_le_set_extended_scan_response_data(uint8_t handle, uint8_t op, uint8_t frag_pref, uint8_t len, uint8_t data[]) { + const uint8_t max_len = sizeof_field(struct bt_hci_cp_le_set_ext_scan_rsp_data, data); + uint8_t valid_len = MIN(len, max_len); + struct bt_hci_cp_le_set_ext_scan_rsp_data params = { + .handle = handle, + .op = op, + .frag_pref = frag_pref, + .len = valid_len, + }; + memcpy(params.data, data, valid_len); + return send_command(BT_HCI_OP_LE_SET_EXT_SCAN_RSP_DATA, sizeof(params) - (max_len - valid_len), ¶ms); +} hci_result_t hci_le_set_scan_response_data(uint8_t len, uint8_t data[]) { struct bt_hci_cp_le_set_scan_rsp_data params = { diff --git a/devices/ble_hci/common-hal/_bleio/hci.h b/devices/ble_hci/common-hal/_bleio/hci.h index fffed7ee151..52da0e246a0 100644 --- a/devices/ble_hci/common-hal/_bleio/hci.h +++ b/devices/ble_hci/common-hal/_bleio/hci.h @@ -68,6 +68,9 @@ hci_result_t hci_le_set_advertising_parameters(uint16_t min_interval, uint16_t m hci_result_t hci_le_set_extended_advertising_data(uint8_t handle, uint8_t op, uint8_t frag_pref, uint8_t len, uint8_t data[]); hci_result_t hci_le_set_extended_advertising_enable(uint8_t enable, uint8_t set_num, uint8_t handle[], uint16_t duration[], uint8_t max_ext_adv_evts[]); hci_result_t hci_le_set_extended_advertising_parameters(uint8_t handle, uint16_t props, uint32_t prim_min_interval, uint32_t prim_max_interval, uint8_t prim_channel_map, uint8_t own_addr_type, bt_addr_le_t *peer_addr, uint8_t filter_policy, int8_t tx_power, uint8_t prim_adv_phy, uint8_t sec_adv_max_skip, uint8_t sec_adv_phy, uint8_t sid, uint8_t scan_req_notify_enable); +// Sends at most one fragment (251 bytes); longer data is truncated. +// Returns HCI_OK or the HCI error status; no output values. +hci_result_t hci_le_set_extended_scan_response_data(uint8_t handle, uint8_t op, uint8_t frag_pref, uint8_t len, uint8_t data[]); hci_result_t hci_le_set_random_address(uint8_t addr[6]); hci_result_t hci_le_set_scan_enable(uint8_t enable, uint8_t filter_dup); diff --git a/docs/environment.rst b/docs/environment.rst index 6ba541ba34c..26823650491 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -99,6 +99,16 @@ Sets the size of the python stack. Must be a multiple of 4. The default value is Increasing the stack reduces the size of the heap available to python code. Used to avoid "Pystack exhausted" errors when the code can't be reworked to avoid it. +CIRCUITPY_USB_HOST_ISO_BUFFER_SIZE (integer) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Allocates a buffer for incoming isochronous USB data (USB camera, USB audio) +on boards with a ``usb_host.Port``. Without it, isochronous IN transfers return at +most one packet per USB frame, so data arriving while code is busy is lost. +The value is rounded up to the next power of two and limited to the range 2048 +to 65536. A typical value for a USB camera is 16384. The buffer is allocated when +the USB host port is created and is never freed. +Only available on rp2 boards with isochronous host support. + CIRCUITPY_WEB_API_PASSWORD (string) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Password required to make modifications to the board from the Web Workflow. diff --git a/docs/library/array.rst b/docs/library/array.rst index 63b86a806c3..e1d474cca4f 100644 --- a/docs/library/array.rst +++ b/docs/library/array.rst @@ -8,7 +8,8 @@ Supported format codes: ``b``, ``B``, ``h``, ``H``, ``i``, ``I``, ``l``, ``L``, ``q``, ``Q``, ``f``, ``d`` (the latter 2 depending on the -floating-point support). +floating-point support). ``d`` is not available on non-full builds, such as +most SAMD21 boards. Classes ------- diff --git a/docs/library/builtins.rst b/docs/library/builtins.rst index 148c1513f3d..c8e05d895e5 100644 --- a/docs/library/builtins.rst +++ b/docs/library/builtins.rst @@ -37,6 +37,35 @@ Functions and types |see_cpython| `python:bytes`. + .. method:: bytes.decode(encoding='utf-8', errors='strict') + + Decode the bytes object to a string using the specified *encoding*. + + MicroPython supports the following encodings: + + - ``'utf-8'`` or ``'utf8'`` - UTF-8 encoding (default) + - ``'ascii'`` - ASCII encoding (subset of UTF-8) + + The *errors* parameter controls how decoding errors are handled: + + - ``'strict'`` - Raise a ``UnicodeError`` on invalid UTF-8 (default) + - ``'ignore'`` - Skip invalid bytes (requires ``MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS``) + - ``'replace'`` - Replace invalid bytes with U+FFFD '�' (requires ``MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS``) + + .. note:: + Error handler support depends on build configuration. On constrained + systems, only ``'strict'`` mode may be available. + + Example:: + + >>> b'\xc2\xa9 2024'.decode('utf-8') # © symbol + '© 2024' + >>> b'hello\xffworld'.decode('utf-8', 'ignore') # Skip invalid bytes + 'helloworld' + + Raises ``LookupError`` if the encoding is not supported, or + ``UnicodeError`` if the data contains invalid UTF-8 and ``errors='strict'``. + .. function:: callable() .. function:: chr() @@ -152,6 +181,35 @@ Functions and types .. class:: str() + .. method:: str.encode(encoding='utf-8') + + Encode the string to bytes using the specified *encoding*. + + MicroPython supports the following encodings: + + - ``'utf-8'`` or ``'utf8'`` - UTF-8 encoding (default) + - ``'ascii'`` - ASCII encoding (subset of UTF-8) + + Example:: + + >>> '© 2024'.encode('utf-8') # Copyright symbol + b'\xc2\xa9 2024' + + Raises ``LookupError`` if the encoding is not supported. + + .. method:: str.center(width) + + Return a centered string of length *width*. Padding is done using spaces. + + When Unicode support is enabled (``MICROPY_PY_BUILTINS_STR_UNICODE``), this + method counts Unicode characters rather than bytes, ensuring proper alignment + for multi-byte UTF-8 characters. + + Example:: + + >>> 'café'.center(10) # é is 2 bytes in UTF-8 + ' café ' + .. function:: sum() .. function:: super() diff --git a/docs/library/gc.rst b/docs/library/gc.rst index d1625c0d8f3..7e946469f63 100644 --- a/docs/library/gc.rst +++ b/docs/library/gc.rst @@ -2,7 +2,8 @@ ========================================== .. module:: gc - :synopsis: control the garbage collector + :synopsis: control the garbage collector which automatically frees + :ref:`heap memory ` |see_cpython_module| :mod:`python:gc`. @@ -18,6 +19,10 @@ Functions Disable automatic garbage collection. Heap memory can still be allocated, and garbage collection can still be initiated manually using :meth:`gc.collect`. +.. function:: isenabled() + + Returns True if automatic garbage collection is enabled, and False otherwise. + .. function:: collect() Run a garbage collection. @@ -64,3 +69,40 @@ Functions This function is a MicroPython extension. CPython has a similar function - ``set_threshold()``, but due to different GC implementations, its signature and semantics are different. + + Examples + ^^^^^^^^ + + To trigger a garbage collection each time 32768 bytes of RAM have been allocated in total:: + + gc.threshold(32768) + + To restore the default behaviour, only triggering garbage collection when out of memory:: + + gc.threshold(-1) + +Example +------- + +.. code-block:: bash + + >>> import gc + >>> gc.mem_free() # Gets number of bytes free in memory + 8192 + >>> gc.mem_alloc() # Gets number of bytes allocated in memory + 1024 + >>> foo = bytearray(1000) # Create a big array of data + >>> gc.mem_free() # Show that there's less memory available + 7168 + >>> gc.mem_alloc() # Show that there's more memory used + 2048 + >>> del foo # Delete the object + >>> gc.mem_free() # Show that collection hasn't run yet + 7168 + >>> gc.mem_alloc() # That memory is still allocated + 2048 + >>> gc.collect() # Manually run the collection + >>> gc.mem_free() # Now we have reclaimed that memory + 8192 + >>> gc.mem_alloc() # That memory is no longer allocated + 1024 diff --git a/docs/library/io.rst b/docs/library/io.rst index ad1fb03c777..8ebb2e8c63f 100644 --- a/docs/library/io.rst +++ b/docs/library/io.rst @@ -71,8 +71,9 @@ buffered, they aren't in MicroPython. (Indeed, that's one of the cases for which we may introduce buffering support.) Note that for efficiency, MicroPython doesn't provide abstract base -classes corresponding to the hierarchy above, and it's not possible -to implement, or subclass, a stream class in pure Python. +classes corresponding to the hierarchy above. However, the +:class:`IOBase` class can be subclassed to implement custom stream +objects in pure Python. Functions --------- @@ -95,6 +96,68 @@ Classes This is type of a file open in text mode, e.g. using ``open(name, "rt")``. You should not instantiate this class directly. +.. class:: IOBase() + + Base class for implementing custom stream objects in Python. Subclasses + can override ``readinto``, ``write``, and ``ioctl`` to create objects + that work with ``print()``, ``json.dump()``, ``select.poll()``, + ``open()`` via a user filesystem, and other stream consumers. + + .. admonition:: Difference to CPython + :class: attention + + In CPython, ``io.IOBase`` has a much larger API surface. MicroPython's + ``IOBase`` is minimal: the C stream infrastructure provides standard + methods like ``read()``, ``readline()``, ``seek()``, ``close()``, and + ``flush()`` automatically. Subclasses only need to implement the + methods below. + + Subclasses implement some or all of the following methods. The C stream + layer calls these internally when user code calls standard stream + functions. + + .. method:: IOBase.readinto(buf) + + Read data into *buf* (a ``bytearray`` sized by the caller). Return the + number of bytes read, 0 at EOF, or ``None`` if no data is available on + a non-blocking stream. Return a negative errno value (e.g. ``-errno.EIO`` + or ``-1``) to signal an error. + + .. method:: IOBase.write(buf) + + Write *buf* (a ``bytearray``) to the stream. Return the number of + bytes written, or ``None`` if a non-blocking stream cannot accept data. + Return a negative errno value to signal an error. + + .. method:: IOBase.ioctl(op, arg) + + Control the stream and query its properties. The operation to perform + is given by *op* which is one of the following integers: + + - 1 -- flush write buffers (*arg* is unused) + - 3 -- poll for readiness; *arg* is a bitmask of events to check, + return a bitmask of ready events. Poll flags: + + * ``0x0001`` -- data available for reading + * ``0x0004`` -- stream ready for writing + * ``0x0008`` -- error condition + * ``0x0010`` -- hang up (e.g. connection closed) + * ``0x0020`` -- invalid request + + - 4 -- close the stream (*arg* is unused) + - 11 -- return the preferred read buffer size, or 0 (*arg* is unused) + + As a minimum ``ioctl(4, ...)`` should be handled to support stream + closure. Implement ``ioctl(3, ...)`` if the stream will be used with + ``select.poll()`` or ``asyncio``. + + Other operations exist for advanced use cases (2 = seek, 5 = timeout, + 10 = fileno); see ``py/stream.h`` for the full list. + + Must always return an integer. Return 0 for success, or ``-1`` for + unsupported operations. (Returning 0 for an unhandled operation tells + the C layer the operation was processed successfully, which may cause + incorrect behaviour.) .. class:: StringIO([string]) .. class:: BytesIO([string]) @@ -129,3 +192,77 @@ Classes :class: attention These constructors are a MicroPython extension. + +IOBase Examples +--------------- + +A minimal write-only stream that collects output into a buffer:: + + import io + + class MyOutput(io.IOBase): + def __init__(self): + self.data = bytearray() + + def write(self, buf): + self.data.extend(buf) + return len(buf) + + def ioctl(self, op, arg): + if op == 4: # close + return 0 + return -1 + + s = MyOutput() + print("hello", file=s) + print(s.data) # bytearray(b'hello\n') + +A readable stream that can be used with ``select.poll()``:: + + import io, select + from micropython import const + + _MP_STREAM_POLL = const(3) + _MP_STREAM_POLL_RD = const(0x0001) + _MP_STREAM_CLOSE = const(4) + + class RingBuffer(io.IOBase): + def __init__(self, size): + self._buf = bytearray(size) + self._size = size + self._wpos = 0 + self._rpos = 0 + + def _available(self): + return (self._wpos - self._rpos) % self._size + + def put(self, data): + for b in data: + self._buf[self._wpos % self._size] = b + self._wpos = (self._wpos + 1) % self._size + + def readinto(self, buf): + n = min(len(buf), self._available()) + for i in range(n): + buf[i] = self._buf[self._rpos % self._size] + self._rpos = (self._rpos + 1) % self._size + return n + + def ioctl(self, op, arg): + if op == _MP_STREAM_POLL: + if arg & _MP_STREAM_POLL_RD and self._available() > 0: + return _MP_STREAM_POLL_RD + return 0 + if op == _MP_STREAM_CLOSE: + return 0 + return -1 + + rb = RingBuffer(64) + rb.put(b"test data") + + poller = select.poll() + poller.register(rb, select.POLLIN) + for obj, flags in poller.poll(0): + buf = bytearray(16) + n = obj.readinto(buf) + print(buf[:n]) # bytearray(b'test data') diff --git a/docs/llms_txt.py b/docs/llms_txt.py new file mode 100644 index 00000000000..001ff9e24c1 --- /dev/null +++ b/docs/llms_txt.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 Vladimir Smitka +# +# SPDX-License-Identifier: MIT + +"""Write llms.txt, an index of the docs for LLM agents (https://llmstxt.org/). + +It reuses what the HTML build already parsed: one line per page with its title +and first paragraph, in table-of-contents order. Read the Docs serves each page +as Markdown on request, so the index links the normal HTML pages. +""" + +from docutils import nodes +from sphinx import addnodes + +DESCRIPTION_LENGTH = 160 + + +def page_order(env, docname, seen): + """Yield docnames depth-first in table-of-contents order.""" + if docname in seen: + return + seen.add(docname) + yield docname + for child in env.toctree_includes.get(docname, []): + yield from page_order(env, child, seen) + + +def is_skipped(node): + """Raw HTML, parameter lists and notes; an API description counts.""" + if isinstance(node, (nodes.raw, nodes.field_list, nodes.comment)): + return True + # Sphinx derives API descriptions from Admonition too, so keep those. + return isinstance(node, nodes.Admonition) and not isinstance(node, addnodes.desc) + + +def first_paragraph(doctree): + """Plain text of the first paragraph worth using as a description.""" + for paragraph in doctree.findall(nodes.paragraph): + parent = paragraph.parent + while parent is not None and not is_skipped(parent): + parent = parent.parent + if parent is not None: + continue + text = " ".join(paragraph.astext().split()) + if text: + if len(text) > DESCRIPTION_LENGTH: + text = text[: DESCRIPTION_LENGTH - 3].rstrip() + "..." + return text + return "" + + +def write_llms_txt(app, exception): + if exception is not None or app.builder.name not in ("html", "dirhtml"): + return + env = app.env + excluded = set(app.config.llms_txt_exclude) + lines = ["# " + app.config.project, ""] + if app.config.llms_txt_description: + lines += ["> " + app.config.llms_txt_description, ""] + lines += ["## Pages", ""] + seen = set() + ordered = list(page_order(env, app.config.root_doc, seen)) + ordered += sorted(env.found_docs - seen) + for docname in ordered: + if docname in excluded or docname not in env.titles: + continue + title = env.titles[docname].astext().strip() + description = first_paragraph(env.get_doctree(docname)) + link = app.builder.get_target_uri(docname) + entry = "- [{}]({})".format(title, link) + if description: + entry += ": " + description + lines.append(entry) + with open(app.outdir / "llms.txt", "w", encoding="utf-8") as f: + f.write("\n".join(lines) + "\n") + + +def setup(app): + app.add_config_value("llms_txt_description", "", "html") + app.add_config_value("llms_txt_exclude", [], "html") + app.connect("build-finished", write_llms_txt) + return {"version": "1.0", "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/docs/shared_bindings_matrix.py b/docs/shared_bindings_matrix.py index 2f9eb796788..e8f329475d0 100644 --- a/docs/shared_bindings_matrix.py +++ b/docs/shared_bindings_matrix.py @@ -33,14 +33,11 @@ SUPPORTED_PORTS = [ "analog", "atmel-samd", - "broadcom", "cxd56", "espressif", - "litex", "mimxrt10xx", "nordic", "raspberrypi", - "renode", "silabs", "stm", "zephyr-cp", diff --git a/docs/supported_ports.rst b/docs/supported_ports.rst index ca36999f423..20f713e17e0 100644 --- a/docs/supported_ports.rst +++ b/docs/supported_ports.rst @@ -14,14 +14,11 @@ Additional testing is limited. ../ports/analog/README ../ports/atmel-samd/README - ../ports/broadcom/README ../ports/cxd56/README ../ports/espressif/README - ../ports/litex/README ../ports/mimxrt10xx/README ../ports/nordic/README ../ports/raspberrypi/README - ../ports/renode/README ../ports/silabs/README ../ports/stm/README ../ports/unix/README diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index d505ff09ef0..0f2c79fb887 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -56,3 +56,10 @@ all available in the `Adafruit bundle `_ and the `Community bundle `_. Make sure to download a version with 7.0.0 or higher in the filename. + +The error can also come from a ``.mpy`` file that contains native code for a different +kind of chip. On boards that can run native code, ``import foo`` looks for ``foo.py``, +then ``foo..mpy``, then ``foo.mpy``, where ```` is +``microcontroller.cpu.architecture``, for example ``foo.armv7emsp.mpy``. A +``foo..mpy`` built for another architecture or CircuitPython version raises this +error instead of falling back to ``foo.mpy``. diff --git a/docs/workflows.md b/docs/workflows.md index ad676d5678e..efb193a4e19 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -461,6 +461,7 @@ Returns information about the device. * `build_date`: CircuitPython build date. * `board_name`: Human readable name of the board. * `mcu_name`: Human readable name of the microcontroller. +* `architecture`: Name of the native `.mpy` architecture this build can load, as used by `mpy-cross -march`. Omitted when the build cannot load native code. * `board_id`: Board id used in code and on circuitpython.org. * `creator_id`: Creator ID for the board. * `creation_id`: Creation ID for the board, set by the creator. diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index ff130d61b37..040fa770396 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -2,7 +2,8 @@ MPY_DIR = ../../.. # Name of module (different to built-in btree so it can coexist) -MOD = btree_$(ARCH) +MOD_BASE = btree +MOD = $(MOD_BASE)_$(ARCH) # Source files (.c or .py) SRC = btree_c.c @@ -15,6 +16,7 @@ BERKELEY_DB_CONFIG_FILE ?= \"extmod/berkeley-db/berkeley_db_config_port.h\" CFLAGS += -I$(BTREE_DIR)/include CFLAGS += -DBERKELEY_DB_CONFIG_FILE=$(BERKELEY_DB_CONFIG_FILE) CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter +CFLAGS += -Wno-deprecated-non-prototype SRC += $(addprefix $(realpath $(BTREE_DIR))/,\ btree/bt_close.c \ @@ -32,6 +34,28 @@ SRC += $(addprefix $(realpath $(BTREE_DIR))/,\ mpool/mpool.c \ ) +ifeq ($(ARCH),xtensa) +MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld +endif + +# Use our own errno implementation if Picolibc is used +CFLAGS += -D__PICOLIBC_ERRNO_FUNCTION=__errno + +ifeq ($(ARCH),armv6m) +# Link with libgcc.a for division helper functions +LINK_RUNTIME = 1 +endif + +# Strip the architecture name from the internal filename. +MPY_LD_FLAGS = "--source-name=$(MOD_BASE).mpy" + +ifeq ($(ARCH),armv7m) +ifeq ($(findstring clang,$(shell $(CC) --version)),clang) +# Link with libclang_rt.builtins.a for memset/memcpy. +LINK_RUNTIME = 1 +endif +endif + include $(MPY_DIR)/py/dynruntime.mk # btree needs gnu99 defined diff --git a/examples/natmod/btree/btree_c.c b/examples/natmod/btree/btree_c.c index 4f494817e7b..c26dbcc8478 100644 --- a/examples/natmod/btree/btree_c.c +++ b/examples/natmod/btree/btree_c.c @@ -4,19 +4,6 @@ #include -#if !defined(__linux__) -void *memcpy(void *dst, const void *src, size_t n) { - return mp_fun_table.memmove_(dst, src, n); -} -void *memset(void *s, int c, size_t n) { - return mp_fun_table.memset_(s, c, n); -} -#endif - -void *memmove(void *dest, const void *src, size_t n) { - return mp_fun_table.memmove_(dest, src, n); -} - void *malloc(size_t n) { void *ptr = m_malloc(n); return ptr; @@ -45,16 +32,16 @@ int puts(const char *s) { int native_errno; #if defined(__linux__) -int *__errno_location (void) +int *__errno_location(void) #else -int *__errno (void) +int *__errno(void) #endif { return &native_errno; } ssize_t mp_stream_posix_write(void *stream, const void *buf, size_t len) { - mp_obj_base_t* o = stream; + mp_obj_base_t *o = stream; const mp_stream_p_t *stream_p = MP_OBJ_TYPE_GET_SLOT(o->type, protocol); mp_uint_t out_sz = stream_p->write(MP_OBJ_FROM_PTR(stream), buf, len, &native_errno); if (out_sz == MP_STREAM_ERROR) { @@ -65,7 +52,7 @@ ssize_t mp_stream_posix_write(void *stream, const void *buf, size_t len) { } ssize_t mp_stream_posix_read(void *stream, void *buf, size_t len) { - mp_obj_base_t* o = stream; + mp_obj_base_t *o = stream; const mp_stream_p_t *stream_p = MP_OBJ_TYPE_GET_SLOT(o->type, protocol); mp_uint_t out_sz = stream_p->read(MP_OBJ_FROM_PTR(stream), buf, len, &native_errno); if (out_sz == MP_STREAM_ERROR) { @@ -76,7 +63,7 @@ ssize_t mp_stream_posix_read(void *stream, void *buf, size_t len) { } off_t mp_stream_posix_lseek(void *stream, off_t offset, int whence) { - const mp_obj_base_t* o = stream; + const mp_obj_base_t *o = stream; const mp_stream_p_t *stream_p = MP_OBJ_TYPE_GET_SLOT(o->type, protocol); struct mp_stream_seek_t seek_s; seek_s.offset = offset; @@ -89,7 +76,7 @@ off_t mp_stream_posix_lseek(void *stream, off_t offset, int whence) { } int mp_stream_posix_fsync(void *stream) { - mp_obj_base_t* o = stream; + mp_obj_base_t *o = stream; const mp_stream_p_t *stream_p = MP_OBJ_TYPE_GET_SLOT(o->type, protocol); mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_FLUSH, 0, &native_errno); if (res == MP_STREAM_ERROR) { @@ -146,22 +133,22 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a btree_getiter_iternext.getiter = btree_getiter; btree_getiter_iternext.iternext = btree_iternext; - btree_type.base.type = (void*)&mp_fun_table.type_type; + btree_type.base.type = (void *)&mp_fun_table.type_type; btree_type.flags = MP_TYPE_FLAG_ITER_IS_CUSTOM; btree_type.name = MP_QSTR_btree; MP_OBJ_TYPE_SET_SLOT(&btree_type, print, btree_print, 0); MP_OBJ_TYPE_SET_SLOT(&btree_type, iter, &btree_getiter_iternext, 1); MP_OBJ_TYPE_SET_SLOT(&btree_type, binary_op, btree_binary_op, 2); MP_OBJ_TYPE_SET_SLOT(&btree_type, subscr, btree_subscr, 3); - btree_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_close), MP_OBJ_FROM_PTR(&btree_close_obj) }; - btree_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_flush), MP_OBJ_FROM_PTR(&btree_flush_obj) }; - btree_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_get), MP_OBJ_FROM_PTR(&btree_get_obj) }; - btree_locals_dict_table[3] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_put), MP_OBJ_FROM_PTR(&btree_put_obj) }; - btree_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_seq), MP_OBJ_FROM_PTR(&btree_seq_obj) }; - btree_locals_dict_table[5] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_keys), MP_OBJ_FROM_PTR(&btree_keys_obj) }; - btree_locals_dict_table[6] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_values), MP_OBJ_FROM_PTR(&btree_values_obj) }; - btree_locals_dict_table[7] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_items), MP_OBJ_FROM_PTR(&btree_items_obj) }; - MP_OBJ_TYPE_SET_SLOT(&btree_type, locals_dict, (void*)&btree_locals_dict, 4); + btree_locals_dict_table[0] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_close), MP_OBJ_FROM_PTR(&btree_close_obj) }; + btree_locals_dict_table[1] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_flush), MP_OBJ_FROM_PTR(&btree_flush_obj) }; + btree_locals_dict_table[2] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_get), MP_OBJ_FROM_PTR(&btree_get_obj) }; + btree_locals_dict_table[3] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_put), MP_OBJ_FROM_PTR(&btree_put_obj) }; + btree_locals_dict_table[4] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_seq), MP_OBJ_FROM_PTR(&btree_seq_obj) }; + btree_locals_dict_table[5] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_keys), MP_OBJ_FROM_PTR(&btree_keys_obj) }; + btree_locals_dict_table[6] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_values), MP_OBJ_FROM_PTR(&btree_values_obj) }; + btree_locals_dict_table[7] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_items), MP_OBJ_FROM_PTR(&btree_items_obj) }; + MP_OBJ_TYPE_SET_SLOT(&btree_type, locals_dict, (void *)&btree_locals_dict, 4); mp_store_global(MP_QSTR_open, MP_OBJ_FROM_PTR(&btree_open_obj)); mp_store_global(MP_QSTR_INCL, MP_OBJ_NEW_SMALL_INT(FLAG_END_KEY_INCL)); diff --git a/examples/natmod/deflate/Makefile b/examples/natmod/deflate/Makefile index 504130d5723..aa0951e88ef 100644 --- a/examples/natmod/deflate/Makefile +++ b/examples/natmod/deflate/Makefile @@ -2,12 +2,34 @@ MPY_DIR = ../../.. # Name of module (different to built-in uzlib so it can coexist) -MOD = deflate_$(ARCH) +MOD_BASE = deflate +MOD = $(MOD_BASE)_$(ARCH) # Source files (.c or .py) SRC = deflate.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) -ARCH = x64 +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) +ARCH ?= x64 + +ifeq ($(ARCH),armv7m) +ifeq ($(findstring clang,$(shell $(CC) --version)),clang) +# Link with libclang_rt.a for memset +LINK_RUNTIME = 1 +endif +endif + +ifeq ($(ARCH),armv6m) +# Link with libgcc.a or libclang_rt.a for division helper functions +LINK_RUNTIME = 1 +endif + +ifeq ($(ARCH),xtensa) +# Link with libm.a and libgcc.a from the toolchain +LINK_RUNTIME = 1 +MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld +endif + +# Strip the architecture name from the internal filename. +MPY_LD_FLAGS = "--source-name=$(MOD_BASE).mpy" include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/deflate/deflate.c b/examples/natmod/deflate/deflate.c index 9de7e101a76..3c470aca9f0 100644 --- a/examples/natmod/deflate/deflate.c +++ b/examples/natmod/deflate/deflate.c @@ -3,15 +3,6 @@ #include "py/dynruntime.h" -#if !defined(__linux__) -void *memcpy(void *dst, const void *src, size_t n) { - return mp_fun_table.memmove_(dst, src, n); -} -void *memset(void *s, int c, size_t n) { - return mp_fun_table.memset_(s, c, n); -} -#endif - mp_obj_full_type_t deflateio_type; #include "extmod/moddeflate.c" @@ -51,14 +42,14 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a deflateio_type.name = MP_QSTR_DeflateIO; MP_OBJ_TYPE_SET_SLOT(&deflateio_type, make_new, &deflateio_make_new, 0); MP_OBJ_TYPE_SET_SLOT(&deflateio_type, protocol, &deflateio_stream_p, 1); - deflateio_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_read), MP_OBJ_FROM_PTR(&mp_stream_read_obj) }; - deflateio_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_readinto), MP_OBJ_FROM_PTR(&mp_stream_readinto_obj) }; - deflateio_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_readline), MP_OBJ_FROM_PTR(&mp_stream_unbuffered_readline_obj) }; - deflateio_locals_dict_table[3] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_write), MP_OBJ_FROM_PTR(&mp_stream_write_obj) }; - deflateio_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_close), MP_OBJ_FROM_PTR(&mp_stream_close_obj) }; - deflateio_locals_dict_table[5] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR___enter__), MP_OBJ_FROM_PTR(&mp_identity_obj) }; - deflateio_locals_dict_table[6] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR___exit__), MP_OBJ_FROM_PTR(&mp_stream___exit___obj) }; - MP_OBJ_TYPE_SET_SLOT(&deflateio_type, locals_dict, (void*)&deflateio_locals_dict, 2); + deflateio_locals_dict_table[0] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_read), MP_OBJ_FROM_PTR(&mp_stream_read_obj) }; + deflateio_locals_dict_table[1] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_readinto), MP_OBJ_FROM_PTR(&mp_stream_readinto_obj) }; + deflateio_locals_dict_table[2] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_readline), MP_OBJ_FROM_PTR(&mp_stream_unbuffered_readline_obj) }; + deflateio_locals_dict_table[3] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_write), MP_OBJ_FROM_PTR(&mp_stream_write_obj) }; + deflateio_locals_dict_table[4] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_close), MP_OBJ_FROM_PTR(&mp_stream_close_obj) }; + deflateio_locals_dict_table[5] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR___enter__), MP_OBJ_FROM_PTR(&mp_identity_obj) }; + deflateio_locals_dict_table[6] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR___exit__), MP_OBJ_FROM_PTR(&mp_stream___exit___obj) }; + MP_OBJ_TYPE_SET_SLOT(&deflateio_type, locals_dict, (void *)&deflateio_locals_dict, 2); mp_store_global(MP_QSTR___name__, MP_OBJ_NEW_QSTR(MP_QSTR_deflate)); mp_store_global(MP_QSTR_DeflateIO, MP_OBJ_FROM_PTR(&deflateio_type)); diff --git a/examples/natmod/features2/Makefile b/examples/natmod/features2/Makefile index 5ddb74087b7..2129974048f 100644 --- a/examples/natmod/features2/Makefile +++ b/examples/natmod/features2/Makefile @@ -10,6 +10,13 @@ SRC = main.c prod.c test.py # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) ARCH = x64 +ifeq ($(findstring clang,$(shell $(CC) --version)),clang) +ifeq ($(ARCH),$(filter $(ARCH),armv6m armv7m rv32imc)) +# Link with both libc.a and libclang_rt.builtins.a +LINK_CLANG_LIBC = 1 +endif +endif + # Link with libm.a and libgcc.a from the toolchain LINK_RUNTIME = 1 diff --git a/examples/natmod/features4/features4.c b/examples/natmod/features4/features4.c index e64c7f75921..c4d82a1f700 100644 --- a/examples/natmod/features4/features4.c +++ b/examples/natmod/features4/features4.c @@ -68,12 +68,12 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a MP_DYNRUNTIME_INIT_ENTRY // Initialise the type. - mp_type_factorial.base.type = (void*)&mp_type_type; + mp_type_factorial.base.type = (void *)&mp_type_type; mp_type_factorial.flags = MP_TYPE_FLAG_NONE; mp_type_factorial.name = MP_QSTR_Factorial; MP_OBJ_TYPE_SET_SLOT(&mp_type_factorial, make_new, factorial_make_new, 0); - factorial_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_calculate), MP_OBJ_FROM_PTR(&factorial_calculate_obj) }; - MP_OBJ_TYPE_SET_SLOT(&mp_type_factorial, locals_dict, (void*)&factorial_locals_dict, 1); + factorial_locals_dict_table[0] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_calculate), MP_OBJ_FROM_PTR(&factorial_calculate_obj) }; + MP_OBJ_TYPE_SET_SLOT(&mp_type_factorial, locals_dict, (void *)&factorial_locals_dict, 1); // Make the Factorial type available on the module. mp_store_global(MP_QSTR_Factorial, MP_OBJ_FROM_PTR(&mp_type_factorial)); diff --git a/examples/natmod/framebuf/Makefile b/examples/natmod/framebuf/Makefile index 2e2b8159754..86a201d23bd 100644 --- a/examples/natmod/framebuf/Makefile +++ b/examples/natmod/framebuf/Makefile @@ -2,12 +2,32 @@ MPY_DIR = ../../.. # Name of module (different to built-in framebuf so it can coexist) -MOD = framebuf_$(ARCH) +MOD_BASE = framebuf +MOD = $(MOD_BASE)_$(ARCH) # Source files (.c or .py) SRC = framebuf.c -# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) -ARCH = x64 +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc, rv64imc) +ARCH ?= x64 + +ifeq ($(ARCH),armv7m) +ifeq ($(findstring clang,$(shell $(CC) --version)),clang) +# Link with libclang_rt.a for memset +LINK_RUNTIME = 1 +endif +endif + +ifeq ($(ARCH),armv6m) +# Link with libgcc.a for division helper functions +LINK_RUNTIME = 1 +endif + +ifeq ($(ARCH),xtensa) +MPY_EXTERN_SYM_FILE=$(MPY_DIR)/ports/esp8266/boards/eagle.rom.addr.v6.ld +endif + +# Strip the architecture name from the internal filename. +MPY_LD_FLAGS = "--source-name=$(MOD_BASE).mpy" include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/framebuf/framebuf.c b/examples/natmod/framebuf/framebuf.c index 1ba702e33d9..6e672439fe9 100644 --- a/examples/natmod/framebuf/framebuf.c +++ b/examples/natmod/framebuf/framebuf.c @@ -3,12 +3,6 @@ #include "py/dynruntime.h" -#if !defined(__linux__) -void *memset(void *s, int c, size_t n) { - return mp_fun_table.memset_(s, c, n); -} -#endif - mp_obj_full_type_t mp_type_framebuf; #include "extmod/modframebuf.c" @@ -19,23 +13,23 @@ static MP_DEFINE_CONST_DICT(framebuf_locals_dict, framebuf_locals_dict_table); mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { MP_DYNRUNTIME_INIT_ENTRY - mp_type_framebuf.base.type = (void*)&mp_type_type; + mp_type_framebuf.base.type = (void *)&mp_type_type; mp_type_framebuf.name = MP_QSTR_FrameBuffer; MP_OBJ_TYPE_SET_SLOT(&mp_type_framebuf, make_new, framebuf_make_new, 0); MP_OBJ_TYPE_SET_SLOT(&mp_type_framebuf, buffer, framebuf_get_buffer, 1); - framebuf_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_fill), MP_OBJ_FROM_PTR(&framebuf_fill_obj) }; - framebuf_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_fill_rect), MP_OBJ_FROM_PTR(&framebuf_fill_rect_obj) }; - framebuf_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_pixel), MP_OBJ_FROM_PTR(&framebuf_pixel_obj) }; - framebuf_locals_dict_table[3] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_hline), MP_OBJ_FROM_PTR(&framebuf_hline_obj) }; - framebuf_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_vline), MP_OBJ_FROM_PTR(&framebuf_vline_obj) }; - framebuf_locals_dict_table[5] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_rect), MP_OBJ_FROM_PTR(&framebuf_rect_obj) }; - framebuf_locals_dict_table[6] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_line), MP_OBJ_FROM_PTR(&framebuf_line_obj) }; - framebuf_locals_dict_table[7] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_ellipse), MP_OBJ_FROM_PTR(&framebuf_ellipse_obj) }; - framebuf_locals_dict_table[8] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_poly), MP_OBJ_FROM_PTR(&framebuf_poly_obj) }; - framebuf_locals_dict_table[9] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_blit), MP_OBJ_FROM_PTR(&framebuf_blit_obj) }; - framebuf_locals_dict_table[10] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_scroll), MP_OBJ_FROM_PTR(&framebuf_scroll_obj) }; - framebuf_locals_dict_table[11] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_text), MP_OBJ_FROM_PTR(&framebuf_text_obj) }; - MP_OBJ_TYPE_SET_SLOT(&mp_type_framebuf, locals_dict, (void*)&framebuf_locals_dict, 2); + framebuf_locals_dict_table[0] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_fill), MP_OBJ_FROM_PTR(&framebuf_fill_obj) }; + framebuf_locals_dict_table[1] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_fill_rect), MP_OBJ_FROM_PTR(&framebuf_fill_rect_obj) }; + framebuf_locals_dict_table[2] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_pixel), MP_OBJ_FROM_PTR(&framebuf_pixel_obj) }; + framebuf_locals_dict_table[3] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_hline), MP_OBJ_FROM_PTR(&framebuf_hline_obj) }; + framebuf_locals_dict_table[4] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_vline), MP_OBJ_FROM_PTR(&framebuf_vline_obj) }; + framebuf_locals_dict_table[5] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_rect), MP_OBJ_FROM_PTR(&framebuf_rect_obj) }; + framebuf_locals_dict_table[6] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_line), MP_OBJ_FROM_PTR(&framebuf_line_obj) }; + framebuf_locals_dict_table[7] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_ellipse), MP_OBJ_FROM_PTR(&framebuf_ellipse_obj) }; + framebuf_locals_dict_table[8] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_poly), MP_OBJ_FROM_PTR(&framebuf_poly_obj) }; + framebuf_locals_dict_table[9] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_blit), MP_OBJ_FROM_PTR(&framebuf_blit_obj) }; + framebuf_locals_dict_table[10] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_scroll), MP_OBJ_FROM_PTR(&framebuf_scroll_obj) }; + framebuf_locals_dict_table[11] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_text), MP_OBJ_FROM_PTR(&framebuf_text_obj) }; + MP_OBJ_TYPE_SET_SLOT(&mp_type_framebuf, locals_dict, (void *)&framebuf_locals_dict, 2); mp_store_global(MP_QSTR_FrameBuffer, MP_OBJ_FROM_PTR(&mp_type_framebuf)); mp_store_global(MP_QSTR_MVLSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MVLSB)); diff --git a/examples/natmod/heapq/Makefile b/examples/natmod/heapq/Makefile index 61e2fc8fcc0..7068b55dfd0 100644 --- a/examples/natmod/heapq/Makefile +++ b/examples/natmod/heapq/Makefile @@ -2,7 +2,8 @@ MPY_DIR = ../../.. # Name of module (different to built-in heapq so it can coexist) -MOD = heapq_$(ARCH) +MOD_BASE = heapq +MOD = $(MOD_BASE)_$(ARCH) # Source files (.c or .py) SRC = heapq.c @@ -10,4 +11,7 @@ SRC = heapq.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 +# Strip the architecture name from the internal filename. +MPY_LD_FLAGS = "--source-name=$(MOD_BASE).mpy" + include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/random/Makefile b/examples/natmod/random/Makefile index 8abdb66dc87..ae0970604b3 100644 --- a/examples/natmod/random/Makefile +++ b/examples/natmod/random/Makefile @@ -2,7 +2,8 @@ MPY_DIR = ../../.. # Name of module (different to built-in random so it can coexist) -MOD = random_$(ARCH) +MOD_BASE = random +MOD = $(MOD_BASE)_$(ARCH) # Source files (.c or .py) SRC = random.c @@ -10,4 +11,7 @@ SRC = random.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 +# Strip the architecture name from the internal filename. +MPY_LD_FLAGS = "--source-name=$(MOD_BASE).mpy" + include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/random/random.c b/examples/natmod/random/random.c index 92257b8bc68..d73b53e2e7d 100644 --- a/examples/natmod/random/random.c +++ b/examples/natmod/random/random.c @@ -12,7 +12,7 @@ uint8_t yasmarang_dat; mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { MP_DYNRUNTIME_INIT_ENTRY - yasmarang_pad = 0xeda4baba; + yasmarang_pad = 0xeda4baba; yasmarang_n = 69; yasmarang_d = 233; diff --git a/examples/natmod/re/Makefile b/examples/natmod/re/Makefile index 56b08b98868..2596437546d 100644 --- a/examples/natmod/re/Makefile +++ b/examples/natmod/re/Makefile @@ -2,7 +2,8 @@ MPY_DIR = ../../.. # Name of module (different to built-in re so it can coexist) -MOD = re_$(ARCH) +MOD_BASE = re +MOD = $(MOD_BASE)_$(ARCH) # Source files (.c or .py) SRC = re.c @@ -10,4 +11,19 @@ SRC = re.c # Architecture to build for (x86, x64, armv7m, xtensa, xtensawin, rv32imc) ARCH = x64 +ifeq ($(ARCH),armv7m) +ifeq ($(findstring clang,$(shell $(CC) --version)),clang) +# Link with libclang_rt.a for memmove +LINK_RUNTIME = 1 +endif +endif + +ifeq ($(ARCH),armv6m) +# Link with libgcc.a for division helper functions +LINK_RUNTIME = 1 +endif + +# Strip the architecture name from the internal filename. +MPY_LD_FLAGS = "--source-name=$(MOD_BASE).mpy" + include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/re/re.c b/examples/natmod/re/re.c index c0279ee7e81..6c5c65842d6 100644 --- a/examples/natmod/re/re.c +++ b/examples/natmod/re/re.c @@ -32,19 +32,6 @@ void mp_cstack_check(void) { } } -#if !defined(__linux__) -void *memcpy(void *dst, const void *src, size_t n) { - return mp_fun_table.memmove_(dst, src, n); -} -void *memset(void *s, int c, size_t n) { - return mp_fun_table.memset_(s, c, n); -} -#endif - -void *memmove(void *dest, const void *src, size_t n) { - return mp_fun_table.memmove_(dest, src, n); -} - mp_obj_full_type_t match_type; mp_obj_full_type_t re_type; @@ -65,23 +52,23 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a // Because MP_QSTR_start/end/split are static, xtensa and xtensawin will make a small data section // to copy in this key/value pair if they are specified as a struct, so assign them separately. - match_type.base.type = (void*)&mp_fun_table.type_type; + match_type.base.type = (void *)&mp_fun_table.type_type; match_type.name = MP_QSTR_match; MP_OBJ_TYPE_SET_SLOT(&match_type, print, match_print, 0); - match_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_group), MP_OBJ_FROM_PTR(&match_group_obj) }; - match_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_groups), MP_OBJ_FROM_PTR(&match_groups_obj) }; - match_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_span), MP_OBJ_FROM_PTR(&match_span_obj) }; - match_locals_dict_table[3] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_start), MP_OBJ_FROM_PTR(&match_start_obj) }; - match_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_end), MP_OBJ_FROM_PTR(&match_end_obj) }; - MP_OBJ_TYPE_SET_SLOT(&match_type, locals_dict, (void*)&match_locals_dict, 1); - - re_type.base.type = (void*)&mp_fun_table.type_type; + match_locals_dict_table[0] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_group), MP_OBJ_FROM_PTR(&match_group_obj) }; + match_locals_dict_table[1] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_groups), MP_OBJ_FROM_PTR(&match_groups_obj) }; + match_locals_dict_table[2] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_span), MP_OBJ_FROM_PTR(&match_span_obj) }; + match_locals_dict_table[3] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_start), MP_OBJ_FROM_PTR(&match_start_obj) }; + match_locals_dict_table[4] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_end), MP_OBJ_FROM_PTR(&match_end_obj) }; + MP_OBJ_TYPE_SET_SLOT(&match_type, locals_dict, (void *)&match_locals_dict, 1); + + re_type.base.type = (void *)&mp_fun_table.type_type; re_type.name = MP_QSTR_re; MP_OBJ_TYPE_SET_SLOT(&re_type, print, re_print, 0); - re_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_match), MP_OBJ_FROM_PTR(&re_match_obj) }; - re_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_search), MP_OBJ_FROM_PTR(&re_search_obj) }; - re_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_split), MP_OBJ_FROM_PTR(&re_split_obj) }; - MP_OBJ_TYPE_SET_SLOT(&re_type, locals_dict, (void*)&re_locals_dict, 1); + re_locals_dict_table[0] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_match), MP_OBJ_FROM_PTR(&re_match_obj) }; + re_locals_dict_table[1] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_search), MP_OBJ_FROM_PTR(&re_search_obj) }; + re_locals_dict_table[2] = (mp_map_elem_t) { MP_OBJ_NEW_QSTR(MP_QSTR_split), MP_OBJ_FROM_PTR(&re_split_obj) }; + MP_OBJ_TYPE_SET_SLOT(&re_type, locals_dict, (void *)&re_locals_dict, 1); mp_store_global(MP_QSTR_compile, MP_OBJ_FROM_PTR(&mod_re_compile_obj)); mp_store_global(MP_QSTR_match, MP_OBJ_FROM_PTR(&re_match_obj)); diff --git a/extmod/modplatform.h b/extmod/modplatform.h index a155f071cba..2417a007b3a 100644 --- a/extmod/modplatform.h +++ b/extmod/modplatform.h @@ -53,6 +53,8 @@ #else #define MICROPY_PLATFORM_ARCH "riscv" #endif +#elif defined(__loongarch__) && defined(__loongarch64) +#define MICROPY_PLATFORM_ARCH "loongarch64" #else #define MICROPY_PLATFORM_ARCH "" #endif @@ -103,6 +105,9 @@ #elif defined(__ANDROID__) #define MICROPY_PLATFORM_LIBC_LIB "bionic" #define MICROPY_PLATFORM_LIBC_VER MP_STRINGIFY(__ANDROID_API__) +#elif defined(__FreeBSD__) +#define MICROPY_PLATFORM_LIBC_LIB "libc" +#define MICROPY_PLATFORM_LIBC_VER "" #else #define MICROPY_PLATFORM_LIBC_LIB "" #define MICROPY_PLATFORM_LIBC_VER "" @@ -112,6 +117,8 @@ #define MICROPY_PLATFORM_SYSTEM "Android" #elif defined(__linux) #define MICROPY_PLATFORM_SYSTEM "Linux" +#elif defined(__FreeBSD__) +#define MICROPY_PLATFORM_SYSTEM "FreeBSD" #elif defined(__unix__) #define MICROPY_PLATFORM_SYSTEM "Unix" #elif defined(__CYGWIN__) diff --git a/extmod/ulab b/extmod/ulab index 1d3ddd8f522..15e99974707 160000 --- a/extmod/ulab +++ b/extmod/ulab @@ -1 +1 @@ -Subproject commit 1d3ddd8f5228e2c7335dac71f4846e6abb0ec9f9 +Subproject commit 15e999747079205b2415e71a6aa2840b8f85f9dc diff --git a/extmod/vfs.c b/extmod/vfs.c index c4156990bcc..86e96c3da16 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -46,6 +46,37 @@ #include "extmod/vfs_posix.h" #endif +// CIRCUITPY-CHANGE: Shared bindings that take an already-opened file check +// mp_type_fileio for a binary file. That is aliased to the FAT fileio type, +// but files opened on a mounted littlefs (or posix) filesystem are a +// different type, so provide this check that accepts any filesystem's binary +// file type. +bool mp_obj_is_fileio(mp_obj_t file) { + #if MICROPY_VFS_FAT + // CIRCUITPY-CHANGE: mp_type_fileio is only aliased to the FAT fileio type + // by CircuitPython port configs; unix tests reference the FAT type directly. + if (mp_obj_is_type(file, &mp_type_vfs_fat_fileio)) { + return true; + } + #endif + #if MICROPY_VFS_LFS1 + if (mp_obj_is_type(file, &mp_type_vfs_lfs1_fileio)) { + return true; + } + #endif + #if MICROPY_VFS_LFS2 + if (mp_obj_is_type(file, &mp_type_vfs_lfs2_fileio)) { + return true; + } + #endif + #if MICROPY_VFS_POSIX + if (mp_obj_is_type(file, &mp_type_vfs_posix_fileio)) { + return true; + } + #endif + return false; +} + #if CIRCUITPY_SDCARDIO #include "shared-module/sdcardio/__init__.h" @@ -178,7 +209,9 @@ static mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { mp_vfs_blockdev_init(&blockdev, bdev_obj); uint8_t buf[44]; for (size_t block_num = 0; block_num <= 1; ++block_num) { - mp_vfs_blockdev_read_ext(&blockdev, block_num, 8, sizeof(buf), buf); + if (mp_vfs_blockdev_read_ext(&blockdev, block_num, 8, sizeof(buf), buf) != 0) { + continue; + } #if MICROPY_VFS_LFS1 if (memcmp(&buf[32], "littlefs", 8) == 0) { // LFS1 diff --git a/extmod/vfs.h b/extmod/vfs.h index 699232e56cd..8696569d69d 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -41,7 +41,9 @@ #define MP_S_IFREG (0x8000) // these are the values for mp_vfs_blockdev_t.flags +#if MICROPY_VFS_BLOCKDEV_NATIVE #define MP_BLOCKDEV_FLAG_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func +#endif #define MP_BLOCKDEV_FLAG_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount #define MP_BLOCKDEV_FLAG_HAVE_IOCTL (0x0004) // new protocol with ioctl #define MP_BLOCKDEV_FLAG_NO_FILESYSTEM (0x0008) // the block device has no filesystem on it @@ -64,11 +66,20 @@ #define MP_BLOCKDEV_IOCTL_BLOCK_ERASE (6) // Constants for vfs.rom_ioctl() function. +// The 4-arg form of WRITE_PREPARE is only available if GET_MIN_PREPARE returns >0. #define MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS (1) // rom_ioctl(1) #define MP_VFS_ROM_IOCTL_GET_SEGMENT (2) // rom_ioctl(2, ) -#define MP_VFS_ROM_IOCTL_WRITE_PREPARE (3) // rom_ioctl(3, , ) +#define MP_VFS_ROM_IOCTL_WRITE_PREPARE (3) // rom_ioctl(3, , ) or rom_ioctl(3, , , ) #define MP_VFS_ROM_IOCTL_WRITE (4) // rom_ioctl(4, , , ) #define MP_VFS_ROM_IOCTL_WRITE_COMPLETE (5) // rom_ioctl(5, ) +#define MP_VFS_ROM_IOCTL_GET_MIN_PREPARE (6) // rom_ioctl(6, ) + +#if MICROPY_VFS_BLOCKDEV_NATIVE +// Function signatures used when MP_BLOCKDEV_FLAG_NATIVE is set. +// Should return 0 for success, or a negative errno code for failure. +typedef int (*mp_vfs_blockdev_native_readblocks)(uint8_t *, uint32_t, uint32_t); +typedef int (*mp_vfs_blockdev_native_writeblocks)(const uint8_t *, uint32_t, uint32_t); +#endif // At the moment the VFS protocol just has import_stat, but could be extended to other methods typedef struct _mp_vfs_proto_t { @@ -157,4 +168,11 @@ MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_statvfs_obj); mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args); #endif +// CIRCUITPY-CHANGE: Shared bindings that take an already-opened file check +// mp_type_fileio for a binary file. That is aliased to the FAT fileio type in +// py/circuitpy_mpconfig.h, but files opened on a mounted littlefs (or posix) +// filesystem are a different type, so provide this check that accepts any +// filesystem's binary file type. +bool mp_obj_is_fileio(mp_obj_t file); + #endif // MICROPY_INCLUDED_EXTMOD_VFS_H diff --git a/extmod/vfs_blockdev.c b/extmod/vfs_blockdev.c index 2b47db07573..db6fc28e2ec 100644 --- a/extmod/vfs_blockdev.c +++ b/extmod/vfs_blockdev.c @@ -111,7 +111,11 @@ void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) { // Helper function to minimise code size of read/write functions // note the n_args argument is moved to the end for further code size reduction (args keep same position in caller and callee). static int mp_vfs_blockdev_call_rw(mp_obj_t *args, size_t block_num, size_t block_off, size_t len, void *buf, size_t n_args) { + #if MICROPY_PY_BUILTINS_MEMORYVIEW + mp_obj_array_t ar = {{&mp_type_memoryview}, 'B' | MP_OBJ_ARRAY_TYPECODE_FLAG_RW, 0, len, buf}; + #else mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, len, buf}; + #endif args[2] = MP_OBJ_NEW_SMALL_INT(block_num); args[3] = MP_OBJ_FROM_PTR(&ar); args[4] = MP_OBJ_NEW_SMALL_INT(block_off); // ignored for n_args == 2 @@ -120,13 +124,6 @@ static int mp_vfs_blockdev_call_rw(mp_obj_t *args, size_t block_num, size_t bloc if (ret == mp_const_none) { return 0; } else { - // Some block devices return a bool indicating success, so - // convert those to an errno integer code. - if (ret == mp_const_true) { - return 0; - } else if (ret == mp_const_false) { - return -MP_EIO; - } // Block device functions are expected to return 0 on success // and negative integer on errors. Check for positive integer // results as some callers (i.e. littlefs) will produce corrupt @@ -137,14 +134,16 @@ static int mp_vfs_blockdev_call_rw(mp_obj_t *args, size_t block_num, size_t bloc } int mp_vfs_blockdev_read(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, uint8_t *buf) { + #if MICROPY_VFS_BLOCKDEV_NATIVE if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) { // CIRCUITPY-CHANGE: Pass the blockdev object into native readblocks so // it has the corresponding state. mp_uint_t (*f)(mp_obj_t self, uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->readblocks[2]; return f(self->readblocks[1], buf, block_num, num_blocks); - } else { - return mp_vfs_blockdev_call_rw(self->readblocks, block_num, 0, num_blocks * self->block_size, buf, 2); } + #endif + + return mp_vfs_blockdev_call_rw(self->readblocks, block_num, 0, num_blocks * self->block_size, buf, 2); } int mp_vfs_blockdev_read_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, uint8_t *buf) { @@ -157,14 +156,16 @@ int mp_vfs_blockdev_write(mp_vfs_blockdev_t *self, size_t block_num, size_t num_ return -MP_EROFS; } + #if MICROPY_VFS_BLOCKDEV_NATIVE if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) { - // CIRCUITPY-CHANGE: Pass the blockdev object into native readblocks so + // CIRCUITPY-CHANGE: Pass the blockdev object into native writeblocks so // it has the corresponding state. mp_uint_t (*f)(mp_obj_t self, const uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->writeblocks[2]; return f(self->writeblocks[1], buf, block_num, num_blocks); - } else { - return mp_vfs_blockdev_call_rw(self->writeblocks, block_num, 0, num_blocks * self->block_size, (void *)buf, 2); } + #endif + + return mp_vfs_blockdev_call_rw(self->writeblocks, block_num, 0, num_blocks * self->block_size, (void *)buf, 2); } int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, const uint8_t *buf) { @@ -178,6 +179,7 @@ int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t arg) { if (self->flags & MP_BLOCKDEV_FLAG_HAVE_IOCTL) { // CIRCUITPY-CHANGE: Support native IOCTL so it can run outside of the VM. + #if MICROPY_VFS_BLOCKDEV_NATIVE if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) { size_t out_value; bool (*f)(mp_obj_t self, uint32_t, uint32_t, size_t *) = (void *)(uintptr_t)self->u.ioctl[2]; @@ -187,6 +189,7 @@ mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t } return MP_OBJ_NEW_SMALL_INT(out_value); } + #endif // New protocol with ioctl self->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(cmd); self->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(arg); diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index 95ed79eb18b..636b68c5c4b 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -104,7 +104,7 @@ static mp_obj_t fat_vfs_make_new(const mp_obj_type_t *type, size_t n_args, size_ // CIRCUITPY-CHANGE static void verify_fs_writable(fs_user_mount_t *vfs) { - if (!filesystem_is_writable_by_python(vfs)) { + if (!filesystem_is_writable_by_python((supervisor_vfs_t *)vfs)) { mp_raise_OSError(MP_EROFS); } } @@ -430,7 +430,10 @@ static mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs // CIRCUITPY-CHANGE: Use MP_BLOCKDEV_FLAG_USB_WRITABLE instead of writeblocks[0] =/!= MP_OBJ_NULL // to specify read-write. // If readonly to Python, it's writable by USB and vice versa. - filesystem_set_writable_by_usb(self, mp_obj_is_true(readonly)); + // CIRCUITPY-CHANGE: The flag helpers take a supervisor_vfs_t so they work + // for the FAT and littlefs root filesystems alike. A VfsFat is always a + // fs_user_mount_t, whose blockdev prefix matches both vfs kinds. + filesystem_set_writable_by_usb((supervisor_vfs_t *)self, mp_obj_is_true(readonly)); // check if we need to make the filesystem FRESULT res = (self->blockdev.flags & MP_BLOCKDEV_FLAG_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK; @@ -487,7 +490,7 @@ static MP_DEFINE_CONST_FUN_OBJ_3(fat_vfs_utime_obj, vfs_fat_utime); static mp_obj_t vfs_fat_getreadonly(mp_obj_t self_in) { fs_user_mount_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(!filesystem_is_writable_by_python(self)); + return mp_obj_new_bool(!filesystem_is_writable_by_python((supervisor_vfs_t *)self)); } static MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_getreadonly_obj, vfs_fat_getreadonly); diff --git a/extmod/vfs_fat_diskio.c b/extmod/vfs_fat_diskio.c index 591da07eb56..5d99ce24d5b 100644 --- a/extmod/vfs_fat_diskio.c +++ b/extmod/vfs_fat_diskio.c @@ -157,7 +157,7 @@ DRESULT disk_ioctl( // error initialising stat = STA_NOINIT; // CIRCUITPY-CHANGE: writability from Python check - } else if (!filesystem_is_writable_by_python(vfs)) { + } else if (!filesystem_is_writable_by_python((supervisor_vfs_t *)vfs)) { stat = STA_PROTECT; } else { stat = 0; diff --git a/extmod/vfs_fat_file.c b/extmod/vfs_fat_file.c index 2a3021ee9a8..bff30fee4fb 100644 --- a/extmod/vfs_fat_file.c +++ b/extmod/vfs_fat_file.c @@ -102,15 +102,15 @@ static mp_uint_t file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)(uintptr_t)arg; switch (s->whence) { - case 0: // SEEK_SET + case MP_SEEK_SET: f_lseek(&self->fp, s->offset); break; - case 1: // SEEK_CUR + case MP_SEEK_CUR: f_lseek(&self->fp, f_tell(&self->fp) + s->offset); break; - case 2: // SEEK_END + case MP_SEEK_END: f_lseek(&self->fp, f_size(&self->fp) + s->offset); break; } @@ -246,7 +246,7 @@ static mp_obj_t fat_vfs_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_i } assert(self != NULL); - if ((mode & FA_WRITE) != 0 && !filesystem_is_writable_by_python(self)) { + if ((mode & FA_WRITE) != 0 && !filesystem_is_writable_by_python((supervisor_vfs_t *)self)) { mp_raise_OSError(MP_EROFS); } diff --git a/extmod/vfs_lfs.c b/extmod/vfs_lfs.c index 19063d63066..eed6946fc26 100644 --- a/extmod/vfs_lfs.c +++ b/extmod/vfs_lfs.c @@ -26,6 +26,7 @@ #include "py/runtime.h" #include "py/mphal.h" +#include "supervisor/fatfs.h" #if MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) @@ -105,15 +106,6 @@ mp_obj_t mp_vfs_lfs1_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode // Attribute ids for lfs2_attr.type. #define LFS_ATTR_MTIME (1) // 64-bit little endian, nanoseconds since 1970/1/1 -typedef struct _mp_obj_vfs_lfs2_t { - mp_obj_base_t base; - mp_vfs_blockdev_t blockdev; - bool enable_mtime; - vstr_t cur_dir; - struct lfs2_config config; - lfs2_t lfs; -} mp_obj_vfs_lfs2_t; - typedef struct _mp_obj_vfs_lfs2_file_t { mp_obj_base_t base; mp_obj_vfs_lfs2_t *vfs; @@ -128,8 +120,10 @@ const char *mp_vfs_lfs2_make_path(mp_obj_vfs_lfs2_t *self, mp_obj_t path_in); mp_obj_t mp_vfs_lfs2_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in); static void lfs_get_mtime(uint8_t buf[8]) { - // On-disk storage of timestamps uses 1970 as the Epoch, so convert from host's Epoch. - uint64_t ns = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(mp_hal_time_ns()); + // CIRCUITPY-CHANGE: Use the same RTC-based time that get_fattime() gives + // the FAT filesystems, so every filesystem kind stamps files alike. + // On-disk storage of timestamps is 64-bit little endian, ns since 1970/1/1. + uint64_t ns = get_fattime_ns(); // Store "ns" to "buf" in little-endian format (essentially htole64). for (size_t i = 0; i < 8; ++i) { buf[i] = ns; @@ -140,6 +134,50 @@ static void lfs_get_mtime(uint8_t buf[8]) { #include "extmod/vfs_lfsx.c" #include "extmod/vfs_lfsx_file.c" +// CIRCUITPY-CHANGE: Supervisor-facing littlefs mount. The caller prepares the +// mp_obj_vfs_lfs2_t (zeroed, with blockdev callbacks and lfs2_config geometry +// already filled in) and the port allocator for the littlefs caches. This runs +// without the VM or its GC, so no MicroPython allocations may happen here. +mp_obj_t mp_vfs_lfs2_mount_supervisor(mp_obj_vfs_lfs2_t *self, void *(*alloc)(size_t), bool format_allowed, bool *formatted_out, int *mount_err) { + struct lfs2_config *config = &self->config; + + config->block_cycles = 100; + config->cache_size = MIN(config->block_size, (4 * MAX(config->read_size, config->prog_size))); + config->lookahead_size = 32; + config->read_buffer = alloc(config->cache_size); + config->prog_buffer = alloc(config->cache_size); + config->lookahead_buffer = alloc(config->lookahead_size); + if (config->read_buffer == NULL || config->prog_buffer == NULL || config->lookahead_buffer == NULL) { + if (mount_err != NULL) { + *mount_err = LFS2_ERR_NOMEM; + } + return MP_OBJ_NULL; + } + + bool formatted = false; + int ret = lfs2_mount(&self->lfs, config); + if (ret < 0 && format_allowed) { + // Empty or corrupted. Format a fresh filesystem and try again. + ret = lfs2_format(&self->lfs, config); + if (ret >= 0) { + formatted = true; + ret = lfs2_mount(&self->lfs, config); + } + } + if (formatted_out != NULL && formatted) { + // Only ever set true: the caller may have formatted the filesystem + // itself (force reformat) and needs that to survive the mount. + *formatted_out = true; + } + if (mount_err != NULL) { + *mount_err = ret; + } + if (ret < 0) { + return MP_OBJ_NULL; + } + return MP_OBJ_FROM_PTR(self); +} + #endif // MICROPY_VFS_LFS2 #endif // MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) diff --git a/extmod/vfs_lfs.h b/extmod/vfs_lfs.h index 1fdf792f1b3..3cb50b75087 100644 --- a/extmod/vfs_lfs.h +++ b/extmod/vfs_lfs.h @@ -36,4 +36,31 @@ extern const mp_obj_type_t mp_type_vfs_lfs2; extern const mp_obj_type_t mp_type_vfs_lfs2_fileio; extern const mp_obj_type_t mp_type_vfs_lfs2_textio; +// CIRCUITPY-CHANGE: Export the lfs2 VFS object so the supervisor can mount +// littlefs directly (before the VM and its GC are running) using the same +// layout the VFS methods in vfs_lfsx.c expect. +#if MICROPY_VFS_LFS2 +#include "lib/littlefs/lfs2.h" +#include "extmod/vfs.h" + +typedef struct _mp_obj_vfs_lfs2_t { + mp_obj_base_t base; + mp_vfs_blockdev_t blockdev; + bool enable_mtime; + vstr_t cur_dir; + struct lfs2_config config; + lfs2_t lfs; +} mp_obj_vfs_lfs2_t; + +// Mount (and optionally format first) a littlefs filesystem on the +// caller-prepared lfs2_config at self->config. self must be zeroed static or +// VM-heap storage; buffers are allocated via alloc(). Returns self_in on +// success or MP_OBJ_NULL on mount failure (the errno-like lfs2 return code is +// passed out via mount_err). formatted_out is set to true when a fresh +// filesystem was formatted before mounting successfully; it is never cleared, +// so the caller must initialize it to false (and may set it true itself after +// an explicit format). +mp_obj_t mp_vfs_lfs2_mount_supervisor(mp_obj_vfs_lfs2_t *self, void *(*alloc)(size_t), bool format_allowed, bool *formatted_out, int *mount_err); +#endif + #endif // MICROPY_INCLUDED_EXTMOD_VFS_LFS_H diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index bbdd21cfb91..20a9f5e665a 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -37,6 +37,7 @@ #include "py/objstr.h" #include "py/mperrno.h" #include "extmod/vfs.h" +#include "supervisor/filesystem.h" #include "shared/timeutils/timeutils.h" #if !MICROPY_ENABLE_FINALISER @@ -244,8 +245,18 @@ static mp_obj_t MP_VFS_LFSx(ilistdir_func)(size_t n_args, const mp_obj_t *args) } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(MP_VFS_LFSx(ilistdir_obj), 1, 2, MP_VFS_LFSx(ilistdir_func)); +static void MP_VFS_LFSx(verify_fs_writable)(MP_OBJ_VFS_LFSx * self) { + // CIRCUITPY-CHANGE: Honor the supervisor's write protection flags so that + // storage.remount(mount, readonly=True) also applies to littlefs mounts, + // matching what the FAT VFS does. + if (!filesystem_is_writable_by_python((supervisor_vfs_t *)self)) { + mp_raise_OSError(MP_EROFS); + } +} + static mp_obj_t MP_VFS_LFSx(remove)(mp_obj_t self_in, mp_obj_t path_in) { MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in); + MP_VFS_LFSx(verify_fs_writable)(self); const char *path = MP_VFS_LFSx(make_path)(self, path_in); int ret = LFSx_API(remove)(&self->lfs, path); if (ret < 0) { @@ -257,6 +268,7 @@ static MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(remove_obj), MP_VFS_LFSx(remove)); static mp_obj_t MP_VFS_LFSx(rmdir)(mp_obj_t self_in, mp_obj_t path_in) { MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in); + MP_VFS_LFSx(verify_fs_writable)(self); const char *path = MP_VFS_LFSx(make_path)(self, path_in); int ret = LFSx_API(remove)(&self->lfs, path); if (ret < 0) { @@ -268,6 +280,7 @@ static MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(rmdir_obj), MP_VFS_LFSx(rmdir)); static mp_obj_t MP_VFS_LFSx(rename)(mp_obj_t self_in, mp_obj_t path_old_in, mp_obj_t path_new_in) { MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in); + MP_VFS_LFSx(verify_fs_writable)(self); const char *path_old = MP_VFS_LFSx(make_path)(self, path_old_in); const char *path = mp_obj_str_get_str(path_new_in); vstr_t path_new; @@ -287,6 +300,7 @@ static MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(rename_obj), MP_VFS_LFSx(rename)); static mp_obj_t MP_VFS_LFSx(mkdir)(mp_obj_t self_in, mp_obj_t path_o) { MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in); + MP_VFS_LFSx(verify_fs_writable)(self); const char *path = MP_VFS_LFSx(make_path)(self, path_o); int ret = LFSx_API(mkdir)(&self->lfs, path); if (ret < 0) { diff --git a/extmod/vfs_lfsx_file.c b/extmod/vfs_lfsx_file.c index a0e572d80cb..c72fcf5b59d 100644 --- a/extmod/vfs_lfsx_file.c +++ b/extmod/vfs_lfsx_file.c @@ -89,6 +89,12 @@ mp_obj_t MP_VFS_LFSx(file_open)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mod flags = LFSx_MACRO(_O_RDONLY); } + // CIRCUITPY-CHANGE: Writes honor the supervisor's write protection flags, + // like the FAT VFS does, so storage.remount() readonly applies too. + if ((flags & LFSx_MACRO(_O_WRONLY)) != 0) { + MP_VFS_LFSx(verify_fs_writable)(self); + } + #if LFS_BUILD_VERSION == 1 MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, file_buffer, uint8_t, self->lfs.cfg->prog_size, type); #else diff --git a/extmod/vfs_posix.c b/extmod/vfs_posix.c index 27f833e802f..90030db1965 100644 --- a/extmod/vfs_posix.c +++ b/extmod/vfs_posix.c @@ -341,10 +341,14 @@ static mp_obj_t vfs_posix_rename(mp_obj_t self_in, mp_obj_t old_path_in, mp_obj_ vfs_posix_require_writable(self_in); mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in); const char *old_path = vfs_posix_get_path_str(self, old_path_in); + size_t old_path_len = strlen(old_path) + 1; + char *old_path_copy = m_new(char, old_path_len); + memcpy(old_path_copy, old_path, old_path_len); const char *new_path = vfs_posix_get_path_str(self, new_path_in); MP_THREAD_GIL_EXIT(); - int ret = rename(old_path, new_path); + int ret = rename(old_path_copy, new_path); MP_THREAD_GIL_ENTER(); + m_del(char, old_path_copy, old_path_len); if (ret != 0) { mp_raise_OSError(errno); } diff --git a/extmod/vfs_rom_file.c b/extmod/vfs_rom_file.c index 57aca8c5dc7..6d7c47111f5 100644 --- a/extmod/vfs_rom_file.c +++ b/extmod/vfs_rom_file.c @@ -110,11 +110,11 @@ static mp_uint_t vfs_rom_file_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t switch (request) { case MP_STREAM_SEEK: { struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)arg; - if (s->whence == 0) { // SEEK_SET + if (s->whence == MP_SEEK_SET) { self->file_offset = (size_t)s->offset; - } else if (s->whence == 1) { // SEEK_CUR + } else if (s->whence == MP_SEEK_CUR) { self->file_offset += s->offset; - } else { // SEEK_END + } else { // MP_SEEK_END self->file_offset = self->file_size + s->offset; } if (self->file_offset > self->file_size) { diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index 8d18b7d1105..8c65397384b 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -258,7 +258,7 @@ static int lfs2_bd_prog(lfs2_t *lfs2, continue; } - // pcache must have been flushed, either by programming and + // pcache must have been flushed, either by programming an // entire block or manually flushing the pcache LFS2_ASSERT(pcache->block == LFS2_BLOCK_NULL); @@ -286,7 +286,7 @@ static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) { // some operations on paths static inline lfs2_size_t lfs2_path_namelen(const char *path) { - return strcspn(path, "/"); + return (lfs2_size_t)strcspn(path, "/"); } static inline bool lfs2_path_islast(const char *path) { @@ -1291,6 +1291,7 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, // found a match for our fetcher? if ((fmask & tag) == (fmask & ftag)) { + LFS2_ASSERT(cb != NULL); int res = cb(data, tag, &(struct lfs2_diskoff){ dir->pair[0], off+sizeof(tag)}); if (res < 0) { @@ -1501,7 +1502,7 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { name += strspn(name, "/"); } - lfs2_size_t namelen = strcspn(name, "/"); + lfs2_size_t namelen = (lfs2_size_t)strcspn(name, "/"); // skip '.' if (namelen == 1 && memcmp(name, ".", 1) == 0) { @@ -1520,7 +1521,7 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, int depth = 1; while (true) { suffix += strspn(suffix, "/"); - sufflen = strcspn(suffix, "/"); + sufflen = (lfs2_size_t)strcspn(suffix, "/"); if (sufflen == 0) { break; } @@ -1761,7 +1762,7 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { commit->off = noff; // perturb valid bit? - commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24); + commit->ptag = ntag ^ ((lfs2_tag_t)(0x80 & ~eperturb) << 24); // reset crc for next commit commit->crc = 0xffffffff; @@ -3244,10 +3245,12 @@ static int lfs2_file_open_(lfs2_t *lfs2, lfs2_file_t *file, #endif static int lfs2_file_close_(lfs2_t *lfs2, lfs2_file_t *file) { -#ifndef LFS2_READONLY - int err = lfs2_file_sync_(lfs2, file); -#else int err = 0; +#ifndef LFS2_READONLY + // it's not safe to do anything if our file errored + if (!(file->flags & LFS2_F_ERRED)) { + err = lfs2_file_sync_(lfs2, file); + } #endif // remove from list of mdirs @@ -3429,18 +3432,12 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { #ifndef LFS2_READONLY static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file) { - if (file->flags & LFS2_F_ERRED) { - // it's not safe to do anything if our file errored - return 0; - } - int err = lfs2_file_flush(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; return err; } - if ((file->flags & LFS2_F_DIRTY) && !lfs2_pair_isnull(file->m.pair)) { // before we commit metadata, we need sync the disk to make sure @@ -3485,6 +3482,17 @@ static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file) { file->flags &= ~LFS2_F_DIRTY; } + // mark any other file handles as dirty + desync + for (lfs2_file_t *f = (lfs2_file_t*)lfs2->mlist; f; f = f->next) { + if (file != f + && f->type == LFS2_TYPE_REG + && lfs2_pair_cmp(f->m.pair, file->m.pair) == 0 + && f->id == file->id) { + f->flags |= LFS2_F_DUSTY; + } + } + + file->flags &= ~LFS2_F_ERRED & ~LFS2_F_DUSTY; return 0; } #endif @@ -3692,7 +3700,7 @@ static lfs2_ssize_t lfs2_file_write_(lfs2_t *lfs2, lfs2_file_t *file, return nsize; } - file->flags &= ~LFS2_F_ERRED; + file->flags &= ~LFS2_F_ERRED & ~LFS2_F_DUSTY; return nsize; } #endif @@ -4771,7 +4779,8 @@ int lfs2_fs_traverse_(lfs2_t *lfs2, continue; } - if ((f->flags & LFS2_F_DIRTY) && !(f->flags & LFS2_F_INLINE)) { + if (((f->flags & LFS2_F_DIRTY) || (f->flags & LFS2_F_DUSTY)) + && !(f->flags & LFS2_F_INLINE)) { int err = lfs2_ctz_traverse(lfs2, &f->cache, &lfs2->rcache, f->ctz.head, f->ctz.size, cb, data); if (err) { diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index aee0619e932..53f2a1569ee 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -135,14 +135,15 @@ enum lfs2_open_flags { // internally used flags #ifndef LFS2_READONLY - LFS2_F_DIRTY = 0x010000, // File does not match storage - LFS2_F_WRITING = 0x020000, // File has been written since last flush + LFS2_F_DIRTY = 0x00010000, // File does not match storage due to write + LFS2_F_DUSTY = 0x00020000, // File does not match storage due to desync + LFS2_F_WRITING = 0x00040000, // File has been written since last flush #endif - LFS2_F_READING = 0x040000, // File has been read since last flush + LFS2_F_READING = 0x00080000, // File has been read since last flush #ifndef LFS2_READONLY - LFS2_F_ERRED = 0x080000, // An error occurred during write + LFS2_F_ERRED = 0x00100000, // An error occurred during write #endif - LFS2_F_INLINE = 0x100000, // Currently inlined in directory entry + LFS2_F_INLINE = 0x01000000, // Currently inlined in directory entry }; // File seek flags diff --git a/lib/micropython-lib b/lib/micropython-lib index 6ae440a8a14..ee4bb8ff139 160000 --- a/lib/micropython-lib +++ b/lib/micropython-lib @@ -1 +1 @@ -Subproject commit 6ae440a8a144233e6e703f6759b7e7a0afaa37a4 +Subproject commit ee4bb8ff139e24c42b739935fbd8ec7c4d061e02 diff --git a/lib/protomatter b/lib/protomatter index 425b6c7f887..0f51ca23474 160000 --- a/lib/protomatter +++ b/lib/protomatter @@ -1 +1 @@ -Subproject commit 425b6c7f887cca5d24d768d8c80c74ba7eb88dc6 +Subproject commit 0f51ca2347479228d8cb96fd3f410b032fe6dbad diff --git a/lib/re1.5/compilecode.c b/lib/re1.5/compilecode.c index 63ae1e02b8d..77a0b8ff2a6 100644 --- a/lib/re1.5/compilecode.c +++ b/lib/re1.5/compilecode.c @@ -50,6 +50,8 @@ static const char *_compilecode(const char *re, ByteProg *prog, int sizecode) int term = PC; int alt_label = 0; + re1_5_stack_chk(); + for (; *re && *re != ')'; re++) { switch (*re) { case '\\': diff --git a/lib/tinyusb b/lib/tinyusb index 5453ed09f19..147ccf5e91f 160000 --- a/lib/tinyusb +++ b/lib/tinyusb @@ -1 +1 @@ -Subproject commit 5453ed09f19af010b6a361562d967cfa30aaadc6 +Subproject commit 147ccf5e91fa272bdfb1de2f75e96de8ea5c943e diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 36491a7e6d5..d249c88fc7c 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -100,6 +100,10 @@ msgstr "" msgid "ndarray length overflows" msgstr "" +#: extmod/ulab/code/ndarray.c +msgid "maximum number of dimensions is " +msgstr "" + #: extmod/ulab/code/ndarray.c msgid "cannot convert complex type" msgstr "" @@ -154,11 +158,23 @@ msgid "operation is not supported for given type" msgstr "" #: extmod/ulab/code/ndarray.c -msgid "shape must be integer or tuple of integers" +msgid "keyword argument must be tuple of integers" msgstr "" -#: extmod/ulab/code/ndarray.c extmod/ulab/code/numpy/random/random.c -msgid "maximum number of dimensions is " +#: extmod/ulab/code/ndarray.c +msgid "too many axes specified" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "axis index out of bounds" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "repeated indices" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "shape must be integer or tuple of integers" msgstr "" #: extmod/ulab/code/ndarray.c @@ -225,6 +241,31 @@ msgstr "" msgid "not implemented for complex dtype" msgstr "" +#: extmod/ulab/code/numpy/compare.c extmod/ulab/code/user/user.c +#: shared-bindings/_eve/__init__.c +msgid "input must be an ndarray" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "object too deep for desired array" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast array data from dtype" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "minlength must not be negative" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "the weights and list don't have the same length" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast weigths to float" +msgstr "" + #: extmod/ulab/code/numpy/compare.c extmod/ulab/code/numpy/create.c #: extmod/ulab/code/numpy/io/io.c extmod/ulab/code/numpy/transform.c #: extmod/ulab/code/numpy/vector.c @@ -259,7 +300,7 @@ msgstr "" msgid "wrong axis specified" msgstr "" -#: extmod/ulab/code/numpy/create.c +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/utils/utils.c msgid "input arrays are not compatible" msgstr "" @@ -271,6 +312,45 @@ msgstr "" msgid "number of points must be at least 2" msgstr "" +#: extmod/ulab/code/numpy/create.c +msgid "indexing should be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "indexing must be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "too many input arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: extmod/ulab/code/numpy/transform.c +msgid "arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "arguments must be 1D arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "input is not an array" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: ports/espressif/common-hal/pulseio/PulseIn.c +#: shared-bindings/bitmaptools/__init__.c +msgid "index out of range" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "mode should be raise, wrap or clip" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "index must not be negative" +msgstr "" + #: extmod/ulab/code/numpy/create.c msgid "offset must be non-negative and no greater than buffer length" msgstr "" @@ -291,7 +371,7 @@ msgstr "" msgid "FFT is implemented for linear arrays only" msgstr "" -#: extmod/ulab/code/numpy/fft/fft_tools.c +#: extmod/ulab/code/numpy/fft/fft_tools.c extmod/ulab/code/utils/utils.c msgid "input array length must be power of 2" msgstr "" @@ -400,10 +480,6 @@ msgstr "" msgid "axis too long" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/numpy/transform.c -msgid "arguments must be ndarrays" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "cross is defined for 1D arrays of length 3" msgstr "" @@ -412,12 +488,6 @@ msgstr "" msgid "diff argument must be an ndarray" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/ulab_tools.c -#: ports/espressif/common-hal/pulseio/PulseIn.c -#: shared-bindings/bitmaptools/__init__.c -msgid "index out of range" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "differentiation order out of range" msgstr "" @@ -463,10 +533,10 @@ msgid "argument must be None, an integer or a tuple of integers" msgstr "" #: extmod/ulab/code/numpy/random/random.c -msgid "shape must be None, and integer or a tuple of integers" +msgid "shape must be None, an integer or a tuple of integers" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "out has wrong type" msgstr "" @@ -478,7 +548,7 @@ msgstr "" msgid "size must match out.shape when used together" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "output array must be contiguous" msgstr "" @@ -502,7 +572,7 @@ msgstr "" msgid "dimensions do not match" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/utils/utils.c msgid "out must be an ndarray" msgstr "" @@ -538,7 +608,7 @@ msgstr "" msgid "input dtype must be float or complex" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/scipy/integrate/integrate.c msgid "first argument must be a callable" msgstr "" @@ -546,18 +616,36 @@ msgstr "" msgid "wrong output type" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c -msgid "first two arguments must be ndarrays" +#: extmod/ulab/code/scipy/integrate/integrate.c +#, c-format +msgid "can't convert arg %d from %s to float" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c -msgid "input must be a dense ndarray" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "levels needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "steps needs to be a positive integer" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c #: extmod/ulab/code/scipy/optimize/optimize.c msgid "first argument must be a function" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "order needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c +msgid "first two arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c +msgid "input must be a dense ndarray" +msgstr "" + #: extmod/ulab/code/scipy/optimize/optimize.c msgid "function has the same sign at the ends of interval" msgstr "" @@ -622,8 +710,16 @@ msgstr "" msgid "input must be square matrix" msgstr "" -#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c -msgid "input must be an ndarray" +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dtype" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dimension" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong shape" msgstr "" #: extmod/ulab/code/utils/utils.c @@ -638,6 +734,34 @@ msgstr "" msgid "out array is too small" msgstr "" +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is defined for ndarrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is implemented for 1D arrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "out array must be a 1D array of float type" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "input and out arrays must have same length" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be an ndarray" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be a 1D dense float array" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be twice as long as input" +msgstr "" + #: extmod/vfs_fat.c py/moderrno.c msgid "Read-only filesystem" msgstr "" @@ -747,6 +871,7 @@ msgstr "" #: ports/analog/common-hal/busio/UART.c #: ports/atmel-samd/common-hal/busio/UART.c ports/cxd56/common-hal/busio/UART.c #: ports/nordic/common-hal/busio/UART.c ports/stm/common-hal/busio/UART.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "RS485" msgstr "" @@ -759,7 +884,9 @@ msgstr "" msgid "Both RX and TX required for flow control" msgstr "" -#: ports/analog/common-hal/busio/UART.c shared-module/rgbmatrix/RGBMatrix.c +#: ports/analog/common-hal/busio/UART.c +#: ports/raspberrypi/common-hal/usb_host/Port.c +#: shared-module/rgbmatrix/RGBMatrix.c msgid "Failed to allocate %q buffer" msgstr "" @@ -772,7 +899,7 @@ msgid "UART transaction timeout" msgstr "" #: ports/analog/common-hal/busio/UART.c ports/espressif/common-hal/busio/UART.c -#: ports/nordic/common-hal/busio/UART.c +#: ports/nordic/common-hal/busio/UART.c ports/zephyr-cp/common-hal/busio/UART.c msgid "All UART peripherals are in use" msgstr "" @@ -846,6 +973,9 @@ msgstr "" #: ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c +#: ports/zephyr-cp/common-hal/busio/I2C.c +#: ports/zephyr-cp/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "Internal resource(s) in use" msgstr "" @@ -1013,6 +1143,21 @@ msgstr "" msgid "Bus pin %d is already in use" msgstr "" +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/espressif/common-hal/espidf/__init__.c +#: ports/raspberrypi/common-hal/picogame/Display.c +#: shared-bindings/picogame/Canvas.c shared-bindings/picogame/__init__.c +msgid "Operation or feature not supported" +msgstr "" + +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c +#: shared-bindings/bitmapfilter/__init__.c +#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c +#: shared-module/synthio/__init__.c +msgid "%q must be of type %q, not %q" +msgstr "" + #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nordic/common-hal/pulseio/PulseIn.c @@ -1180,11 +1325,6 @@ msgstr "" msgid "Timeout is too long: Maximum timeout length is %d seconds" msgstr "" -#: ports/espressif/common-hal/_bleio/Characteristic.c -#: ports/espressif/common-hal/_bleio/Descriptor.c -msgid "MITM security not supported" -msgstr "" - #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Descriptor.c @@ -1199,6 +1339,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Service.c +#: ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c msgid "Too many %q" msgstr "" @@ -1213,6 +1354,20 @@ msgstr "" msgid "Can't set CCCD on local Characteristic" msgstr "" +#: ports/espressif/common-hal/_bleio/Connection.c +#: ports/espressif/common-hal/_bleio/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c +#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c +#: shared-bindings/_bleio/CharacteristicBuffer.c +msgid "Not connected" +msgstr "" + +#: ports/espressif/common-hal/_bleio/Connection.c +msgid "No pairing in progress" +msgstr "" + #: ports/espressif/common-hal/_bleio/Connection.c #: ports/nordic/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" @@ -1246,15 +1401,6 @@ msgstr "" msgid "Invalid BLE parameter" msgstr "" -#: ports/espressif/common-hal/_bleio/__init__.c -#: ports/nordic/common-hal/_bleio/__init__.c -#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c -#: ports/zephyr-cp/common-hal/_bleio/Connection.c -#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c -#: shared-bindings/_bleio/CharacteristicBuffer.c -msgid "Not connected" -msgstr "" - #: ports/espressif/common-hal/_bleio/__init__.c #: shared-module/audiofilewriter/AudioFileWriter.c msgid "Already in progress" @@ -1380,7 +1526,7 @@ msgstr "" #: ports/espressif/common-hal/busio/I2C.c #: ports/espressif/common-hal/i2ctarget/I2CTarget.c -#: ports/nordic/common-hal/busio/I2C.c +#: ports/nordic/common-hal/busio/I2C.c ports/zephyr-cp/common-hal/busio/I2C.c msgid "All I2C peripherals are in use" msgstr "" @@ -1394,6 +1540,7 @@ msgid "SPI configuration failed" msgstr "" #: ports/espressif/common-hal/busio/SPI.c ports/nordic/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/SPI.c msgid "All SPI peripherals are in use" msgstr "" @@ -1479,12 +1626,6 @@ msgstr "" msgid "Requested resource not found" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c -#: ports/raspberrypi/common-hal/picogame/Display.c -#: shared-bindings/picogame/__init__.c -msgid "Operation or feature not supported" -msgstr "" - #: ports/espressif/common-hal/espidf/__init__.c #: ports/espressif/common-hal/qspibus/QSPIBus.c msgid "Operation timed out" @@ -1980,13 +2121,6 @@ msgstr "" msgid "All timers for this pin are in use" msgstr "" -#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c -#: shared-bindings/bitmapfilter/__init__.c -#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c -#: shared-module/synthio/__init__.c -msgid "%q must be of type %q, not %q" -msgstr "" - #: ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c msgid "Pins must be sequential GPIO pins" msgstr "" @@ -2407,6 +2541,10 @@ msgstr "" msgid "bad typecode" msgstr "" +#: py/binary.c +msgid "integer out of range" +msgstr "" + #: py/builtinevex.c msgid "bad compile mode" msgstr "" @@ -2525,6 +2663,10 @@ msgstr "" msgid "* arg after **" msgstr "" +#: py/compile.c +msgid "* arg after kwarg" +msgstr "" + #: py/compile.c msgid "too many args" msgstr "" @@ -2653,6 +2795,22 @@ msgstr "" msgid "opcode '%q' argument %d: must not be zero" msgstr "" +#: py/emitinlinerv32.c +msgid "opcode '%q': registers must be different" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': malformed register list" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': invalid stack adjustment" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q' argument %d: wrong register(s)" +msgstr "" + #: py/emitinlinerv32.c msgid "invalid RV32 instruction '%q'" msgstr "" @@ -2814,6 +2972,10 @@ msgstr "" msgid "return expected '%q' but got '%q'" msgstr "" +#: py/emitnative.c +msgid "native raise" +msgstr "" + #: py/emitnative.c msgid "must raise an object" msgstr "" @@ -2826,8 +2988,8 @@ msgstr "" msgid "unicode name escapes" msgstr "" -#: py/modbuiltins.c -msgid "chr() arg not in range(0x110000)" +#: py/modbuiltins.c py/objstr.c +msgid "char not in range(0x110000)" msgstr "" #: py/modbuiltins.c @@ -3130,11 +3292,6 @@ msgstr "" msgid "float too big" msgstr "" -#: py/objint.c -#, c-format -msgid "value must fit in %d byte(s)" -msgstr "" - #: py/objint.c shared-bindings/time/__init__.c msgid "No long integer support" msgstr "" @@ -3148,6 +3305,15 @@ msgstr "" msgid "%q=%q" msgstr "" +#: py/objint_impl.h +#, c-format +msgid "value would overflow a %d byte buffer" +msgstr "" + +#: py/objint_impl.h +msgid "can't convert negative int to unsigned" +msgstr "" + #: py/objint_longlong.c py/parsenum.c msgid "result overflows long long storage" msgstr "" @@ -3188,6 +3354,10 @@ msgstr "" msgid "Cannot subclass slice" msgstr "" +#: py/objstr.c +msgid "unknown encoding: %q" +msgstr "" + #: py/objstr.c msgid "bytes value out of range" msgstr "" @@ -3766,12 +3936,6 @@ msgstr "" msgid "Length of %q must be an even multiple of channel_count * type_size" msgstr "" -#: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c -#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c -#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c -msgid "file must be a file opened in byte mode" -msgstr "" - #: shared-bindings/audiodelays/Chorus.c shared-bindings/audiodelays/Echo.c #: shared-bindings/audiodelays/Flanger.c #: shared-bindings/audiodelays/GranularPitchShift.c @@ -3800,6 +3964,12 @@ msgstr "" msgid "%q and %q must be different" msgstr "" +#: shared-bindings/audiomp3/MP3Decoder.c +#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c +#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c +msgid "file must be a file opened in byte mode" +msgstr "" + #: shared-bindings/bitmapfilter/__init__.c msgid "" "weights must be a sequence with an odd square number of elements (usually 9 " @@ -4035,10 +4205,14 @@ msgstr "" msgid "System entry must be gnss.SatelliteSystem" msgstr "" -#: shared-bindings/hashlib/__init__.c +#: shared-bindings/hashlib/__init__.c shared-bindings/hmac/__init__.c msgid "Unsupported hash algorithm" msgstr "" +#: shared-bindings/hmac/__init__.c +msgid "hardware key slot is unused" +msgstr "" + #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr "" @@ -4150,28 +4324,6 @@ msgstr "" msgid "Publishers can only be created from a parent node" msgstr "" -#: shared-bindings/rgbmatrix/RGBMatrix.c -msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] is not on the same port as clock" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] duplicates another pin assignment" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "" -"Pinout uses %d bytes per element, which consumes more than the ideal %d " -"bytes. If this cannot be avoided, pass allow_inefficient=True to the " -"constructor" -msgstr "" - #: shared-bindings/rgbmatrix/RGBMatrix.c #, c-format msgid "Must use a multiple of 6 rgb pins, not %d" @@ -4250,10 +4402,6 @@ msgstr "" msgid "%q must be a subclass of %q" msgstr "" -#: shared-bindings/wifi/Monitor.c -msgid "%q out of bounds" -msgstr "" - #: shared-bindings/wifi/Radio.c msgid "Invalid hex password" msgstr "" @@ -4324,7 +4472,7 @@ msgstr "" msgid "Failed to parse MP3 file" msgstr "" -#: shared-module/bitbangio/I2C.c +#: shared-module/bitbangio/I2C.c shared-module/msgpack/__init__.c msgid "%q too long" msgstr "" @@ -4382,6 +4530,14 @@ msgstr "" msgid "unsupported colorspace for GifWriter" msgstr "" +#: shared-module/hmac/HMAC.c +msgid "key does not support this digest" +msgstr "" + +#: shared-module/hmac/HMAC.c +msgid "HMAC of an empty message is not supported with a hardware key" +msgstr "" + #: shared-module/i2cdisplaybus/I2CDisplayBus.c #: shared-module/is31fl3741/IS31FL3741.c #, c-format @@ -4455,6 +4611,28 @@ msgid "" "requires contiguous pins." msgstr "" +#: shared-module/rgbmatrix/RGBMatrix.c +msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] is not on the same port as clock" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] duplicates another pin assignment" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "" +"Pinout uses %d bytes per element, which consumes more than the ideal %d " +"bytes. If this cannot be avoided, pass allow_inefficient=True to the " +"constructor" +msgstr "" + #: shared-module/rgbmatrix/RGBMatrix.c msgid "No timer available" msgstr "" diff --git a/locale/cs.po b/locale/cs.po index 1d8bedfee47..e851044c7f4 100644 --- a/locale/cs.po +++ b/locale/cs.po @@ -100,6 +100,10 @@ msgstr "pole je příliš velké" msgid "ndarray length overflows" msgstr "délka ndarray přetekla" +#: extmod/ulab/code/ndarray.c +msgid "maximum number of dimensions is " +msgstr "maximální počet dimenzí je " + #: extmod/ulab/code/ndarray.c msgid "cannot convert complex type" msgstr "nelze převést typ complex" @@ -153,14 +157,26 @@ msgstr "" msgid "operation is not supported for given type" msgstr "" +#: extmod/ulab/code/ndarray.c +msgid "keyword argument must be tuple of integers" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "too many axes specified" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "axis index out of bounds" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "repeated indices" +msgstr "" + #: extmod/ulab/code/ndarray.c msgid "shape must be integer or tuple of integers" msgstr "tvar musí být integer nebo tuple integerů" -#: extmod/ulab/code/ndarray.c extmod/ulab/code/numpy/random/random.c -msgid "maximum number of dimensions is " -msgstr "maximální počet dimenzí je " - #: extmod/ulab/code/ndarray.c msgid "can only specify one unknown dimension" msgstr "" @@ -225,6 +241,31 @@ msgstr "vstup musí být 1D ndarray" msgid "not implemented for complex dtype" msgstr "není implementováno pro komplexní dtype" +#: extmod/ulab/code/numpy/compare.c extmod/ulab/code/user/user.c +#: shared-bindings/_eve/__init__.c +msgid "input must be an ndarray" +msgstr "vstup musí být ndarray" + +#: extmod/ulab/code/numpy/compare.c +msgid "object too deep for desired array" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast array data from dtype" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "minlength must not be negative" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "the weights and list don't have the same length" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast weigths to float" +msgstr "" + #: extmod/ulab/code/numpy/compare.c extmod/ulab/code/numpy/create.c #: extmod/ulab/code/numpy/io/io.c extmod/ulab/code/numpy/transform.c #: extmod/ulab/code/numpy/vector.c @@ -259,7 +300,7 @@ msgstr "pouze ndarraye mohou být spojeny" msgid "wrong axis specified" msgstr "" -#: extmod/ulab/code/numpy/create.c +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/utils/utils.c msgid "input arrays are not compatible" msgstr "vstupní pole nejsou kompatibilní" @@ -271,6 +312,45 @@ msgstr "vstup musí být 1- nebo 2-d" msgid "number of points must be at least 2" msgstr "" +#: extmod/ulab/code/numpy/create.c +msgid "indexing should be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "indexing must be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "too many input arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: extmod/ulab/code/numpy/transform.c +msgid "arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "arguments must be 1D arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "input is not an array" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: ports/espressif/common-hal/pulseio/PulseIn.c +#: shared-bindings/bitmaptools/__init__.c +msgid "index out of range" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "mode should be raise, wrap or clip" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "index must not be negative" +msgstr "" + #: extmod/ulab/code/numpy/create.c msgid "offset must be non-negative and no greater than buffer length" msgstr "" @@ -291,7 +371,7 @@ msgstr "FFT lze použít pouze pro ndarrays" msgid "FFT is implemented for linear arrays only" msgstr "FFT je implementován pouze pro lineární pole" -#: extmod/ulab/code/numpy/fft/fft_tools.c +#: extmod/ulab/code/numpy/fft/fft_tools.c extmod/ulab/code/utils/utils.c msgid "input array length must be power of 2" msgstr "" @@ -400,10 +480,6 @@ msgstr "argsort není implementován pro zploštěná pole" msgid "axis too long" msgstr "osa je příliš dlouhá" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/numpy/transform.c -msgid "arguments must be ndarrays" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "cross is defined for 1D arrays of length 3" msgstr "" @@ -412,12 +488,6 @@ msgstr "" msgid "diff argument must be an ndarray" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/ulab_tools.c -#: ports/espressif/common-hal/pulseio/PulseIn.c -#: shared-bindings/bitmaptools/__init__.c -msgid "index out of range" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "differentiation order out of range" msgstr "" @@ -463,10 +533,10 @@ msgid "argument must be None, an integer or a tuple of integers" msgstr "" #: extmod/ulab/code/numpy/random/random.c -msgid "shape must be None, and integer or a tuple of integers" +msgid "shape must be None, an integer or a tuple of integers" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "out has wrong type" msgstr "" @@ -478,7 +548,7 @@ msgstr "" msgid "size must match out.shape when used together" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "output array must be contiguous" msgstr "" @@ -502,7 +572,7 @@ msgstr "" msgid "dimensions do not match" msgstr "dimenze nesouhlasí" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/utils/utils.c msgid "out must be an ndarray" msgstr "" @@ -538,7 +608,7 @@ msgstr "nelze převést complex na float" msgid "input dtype must be float or complex" msgstr "vstupní dtype musí být float nebo complex" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/scipy/integrate/integrate.c msgid "first argument must be a callable" msgstr "První argument musí být zavolatelný" @@ -546,6 +616,28 @@ msgstr "První argument musí být zavolatelný" msgid "wrong output type" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c +#, c-format +msgid "can't convert arg %d from %s to float" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "levels needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "steps needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +#: extmod/ulab/code/scipy/optimize/optimize.c +msgid "first argument must be a function" +msgstr "první argument musí být funkce" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "order needs to be a positive integer" +msgstr "" + #: extmod/ulab/code/scipy/linalg/linalg.c msgid "first two arguments must be ndarrays" msgstr "první dva argumenty musí být ndarray" @@ -554,10 +646,6 @@ msgstr "první dva argumenty musí být ndarray" msgid "input must be a dense ndarray" msgstr "vstup musí být hustý ndarray" -#: extmod/ulab/code/scipy/optimize/optimize.c -msgid "first argument must be a function" -msgstr "první argument musí být funkce" - #: extmod/ulab/code/scipy/optimize/optimize.c msgid "function has the same sign at the ends of interval" msgstr "" @@ -622,9 +710,17 @@ msgstr "" msgid "input must be square matrix" msgstr "" -#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c -msgid "input must be an ndarray" -msgstr "vstup musí být ndarray" +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dtype" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dimension" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong shape" +msgstr "" #: extmod/ulab/code/utils/utils.c msgid "out must be a float dense array" @@ -638,6 +734,34 @@ msgstr "offset je příliš velký" msgid "out array is too small" msgstr "výstupní pole je příliš malé" +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is defined for ndarrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is implemented for 1D arrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "out array must be a 1D array of float type" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "input and out arrays must have same length" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be an ndarray" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be a 1D dense float array" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be twice as long as input" +msgstr "" + #: extmod/vfs_fat.c py/moderrno.c msgid "Read-only filesystem" msgstr "Filesystém pouze pro čtení" @@ -755,6 +879,7 @@ msgstr "" #: ports/analog/common-hal/busio/UART.c #: ports/atmel-samd/common-hal/busio/UART.c ports/cxd56/common-hal/busio/UART.c #: ports/nordic/common-hal/busio/UART.c ports/stm/common-hal/busio/UART.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "RS485" msgstr "RS485" @@ -767,7 +892,9 @@ msgstr "" msgid "Both RX and TX required for flow control" msgstr "RX a TX jsou vyžadovány pro kontrolu toku" -#: ports/analog/common-hal/busio/UART.c shared-module/rgbmatrix/RGBMatrix.c +#: ports/analog/common-hal/busio/UART.c +#: ports/raspberrypi/common-hal/usb_host/Port.c +#: shared-module/rgbmatrix/RGBMatrix.c msgid "Failed to allocate %q buffer" msgstr "Chyba alokace %q bufferu" @@ -780,7 +907,7 @@ msgid "UART transaction timeout" msgstr "" #: ports/analog/common-hal/busio/UART.c ports/espressif/common-hal/busio/UART.c -#: ports/nordic/common-hal/busio/UART.c +#: ports/nordic/common-hal/busio/UART.c ports/zephyr-cp/common-hal/busio/UART.c msgid "All UART peripherals are in use" msgstr "Všechny UART periferie jsou používány" @@ -854,6 +981,9 @@ msgstr "Všechny časovače jsou používány" #: ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c +#: ports/zephyr-cp/common-hal/busio/I2C.c +#: ports/zephyr-cp/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "Internal resource(s) in use" msgstr "" @@ -1021,6 +1151,21 @@ msgstr "Datový pin 0 musí být zarovnán na bajty" msgid "Bus pin %d is already in use" msgstr "Sběrnicový pin %d je již používán" +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/espressif/common-hal/espidf/__init__.c +#: ports/raspberrypi/common-hal/picogame/Display.c +#: shared-bindings/picogame/Canvas.c shared-bindings/picogame/__init__.c +msgid "Operation or feature not supported" +msgstr "Operace nebo funkce není podporována" + +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c +#: shared-bindings/bitmapfilter/__init__.c +#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c +#: shared-module/synthio/__init__.c +msgid "%q must be of type %q, not %q" +msgstr "%q musí být typu %q, ne %q" + #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nordic/common-hal/pulseio/PulseIn.c @@ -1188,11 +1333,6 @@ msgstr "Data nejsou podporována s cíleným oznamováním" msgid "Timeout is too long: Maximum timeout length is %d seconds" msgstr "Časový limit je příliš dlouhý: maximální limit je %d vteřin" -#: ports/espressif/common-hal/_bleio/Characteristic.c -#: ports/espressif/common-hal/_bleio/Descriptor.c -msgid "MITM security not supported" -msgstr "" - #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Descriptor.c @@ -1207,6 +1347,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Service.c +#: ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c msgid "Too many %q" msgstr "" @@ -1221,6 +1362,20 @@ msgstr "Žádné CCCD pro tuto charakteristiku" msgid "Can't set CCCD on local Characteristic" msgstr "Nelze nastavit CCCD na místní charakteristiku" +#: ports/espressif/common-hal/_bleio/Connection.c +#: ports/espressif/common-hal/_bleio/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c +#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c +#: shared-bindings/_bleio/CharacteristicBuffer.c +msgid "Not connected" +msgstr "Nepřipojený" + +#: ports/espressif/common-hal/_bleio/Connection.c +msgid "No pairing in progress" +msgstr "" + #: ports/espressif/common-hal/_bleio/Connection.c #: ports/nordic/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" @@ -1254,15 +1409,6 @@ msgstr "" msgid "Invalid BLE parameter" msgstr "Chybný BLE parametr" -#: ports/espressif/common-hal/_bleio/__init__.c -#: ports/nordic/common-hal/_bleio/__init__.c -#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c -#: ports/zephyr-cp/common-hal/_bleio/Connection.c -#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c -#: shared-bindings/_bleio/CharacteristicBuffer.c -msgid "Not connected" -msgstr "Nepřipojený" - #: ports/espressif/common-hal/_bleio/__init__.c #: shared-module/audiofilewriter/AudioFileWriter.c msgid "Already in progress" @@ -1391,7 +1537,7 @@ msgstr "" #: ports/espressif/common-hal/busio/I2C.c #: ports/espressif/common-hal/i2ctarget/I2CTarget.c -#: ports/nordic/common-hal/busio/I2C.c +#: ports/nordic/common-hal/busio/I2C.c ports/zephyr-cp/common-hal/busio/I2C.c msgid "All I2C peripherals are in use" msgstr "Všechny I2C periferie jsou používány" @@ -1405,6 +1551,7 @@ msgid "SPI configuration failed" msgstr "Konfigurace SPI selhala" #: ports/espressif/common-hal/busio/SPI.c ports/nordic/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/SPI.c msgid "All SPI peripherals are in use" msgstr "Všechny SPI periferie jsou používány" @@ -1490,12 +1637,6 @@ msgstr "Chybná velikost" msgid "Requested resource not found" msgstr "Požadovaný zdroj nebyl nalezen" -#: ports/espressif/common-hal/espidf/__init__.c -#: ports/raspberrypi/common-hal/picogame/Display.c -#: shared-bindings/picogame/__init__.c -msgid "Operation or feature not supported" -msgstr "Operace nebo funkce není podporována" - #: ports/espressif/common-hal/espidf/__init__.c #: ports/espressif/common-hal/qspibus/QSPIBus.c msgid "Operation timed out" @@ -1993,13 +2134,6 @@ msgstr "Není možné zapisovat do paměti jen pro čtení" msgid "All timers for this pin are in use" msgstr "Všechny časovače pro tento pin jsou používány" -#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c -#: shared-bindings/bitmapfilter/__init__.c -#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c -#: shared-module/synthio/__init__.c -msgid "%q must be of type %q, not %q" -msgstr "%q musí být typu %q, ne %q" - #: ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c msgid "Pins must be sequential GPIO pins" msgstr "" @@ -2421,6 +2555,10 @@ msgstr "funkci chybí argument pouze pro klíčové slovo" msgid "bad typecode" msgstr "" +#: py/binary.c +msgid "integer out of range" +msgstr "" + #: py/builtinevex.c msgid "bad compile mode" msgstr "" @@ -2544,6 +2682,10 @@ msgstr "" msgid "* arg after **" msgstr "* arg po **" +#: py/compile.c +msgid "* arg after kwarg" +msgstr "" + #: py/compile.c msgid "too many args" msgstr "příliš mnoho argumentů" @@ -2672,6 +2814,22 @@ msgstr "" msgid "opcode '%q' argument %d: must not be zero" msgstr "" +#: py/emitinlinerv32.c +msgid "opcode '%q': registers must be different" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': malformed register list" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': invalid stack adjustment" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q' argument %d: wrong register(s)" +msgstr "" + #: py/emitinlinerv32.c msgid "invalid RV32 instruction '%q'" msgstr "" @@ -2833,6 +2991,10 @@ msgstr "" msgid "return expected '%q' but got '%q'" msgstr "" +#: py/emitnative.c +msgid "native raise" +msgstr "" + #: py/emitnative.c msgid "must raise an object" msgstr "" @@ -2845,8 +3007,8 @@ msgstr "" msgid "unicode name escapes" msgstr "" -#: py/modbuiltins.c -msgid "chr() arg not in range(0x110000)" +#: py/modbuiltins.c py/objstr.c +msgid "char not in range(0x110000)" msgstr "" #: py/modbuiltins.c @@ -3149,11 +3311,6 @@ msgstr "" msgid "float too big" msgstr "" -#: py/objint.c -#, c-format -msgid "value must fit in %d byte(s)" -msgstr "" - #: py/objint.c shared-bindings/time/__init__.c msgid "No long integer support" msgstr "Není podpora long integer" @@ -3167,6 +3324,15 @@ msgstr "" msgid "%q=%q" msgstr "%q=%q" +#: py/objint_impl.h +#, c-format +msgid "value would overflow a %d byte buffer" +msgstr "" + +#: py/objint_impl.h +msgid "can't convert negative int to unsigned" +msgstr "" + #: py/objint_longlong.c py/parsenum.c msgid "result overflows long long storage" msgstr "" @@ -3207,6 +3373,10 @@ msgstr "%q krok nemůže být nula" msgid "Cannot subclass slice" msgstr "Nelze použít řez podtřídy" +#: py/objstr.c +msgid "unknown encoding: %q" +msgstr "" + #: py/objstr.c msgid "bytes value out of range" msgstr "" @@ -3786,12 +3956,6 @@ msgstr "%q musí být bytearray nebo pole typu 'h', 'H', 'b', nebo 'B'" msgid "Length of %q must be an even multiple of channel_count * type_size" msgstr "" -#: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c -#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c -#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c -msgid "file must be a file opened in byte mode" -msgstr "" - #: shared-bindings/audiodelays/Chorus.c shared-bindings/audiodelays/Echo.c #: shared-bindings/audiodelays/Flanger.c #: shared-bindings/audiodelays/GranularPitchShift.c @@ -3820,6 +3984,12 @@ msgstr "" msgid "%q and %q must be different" msgstr "%q a %q musí být rozdílné" +#: shared-bindings/audiomp3/MP3Decoder.c +#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c +#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c +msgid "file must be a file opened in byte mode" +msgstr "" + #: shared-bindings/bitmapfilter/__init__.c msgid "" "weights must be a sequence with an odd square number of elements (usually 9 " @@ -4055,10 +4225,14 @@ msgstr "Buffer není bytearray." msgid "System entry must be gnss.SatelliteSystem" msgstr "Parametr \"system\" musí být gnss.SatelliteSystem" -#: shared-bindings/hashlib/__init__.c +#: shared-bindings/hashlib/__init__.c shared-bindings/hmac/__init__.c msgid "Unsupported hash algorithm" msgstr "Nepodporovaný hash algoritmus" +#: shared-bindings/hmac/__init__.c +msgid "hardware key slot is unused" +msgstr "" + #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr "" @@ -4170,28 +4344,6 @@ msgstr "" msgid "Publishers can only be created from a parent node" msgstr "" -#: shared-bindings/rgbmatrix/RGBMatrix.c -msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" -msgstr "Počet prvků rgb_pin musí být 6, 12, 18, 24, nebo 30" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] is not on the same port as clock" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] duplicates another pin assignment" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "" -"Pinout uses %d bytes per element, which consumes more than the ideal %d " -"bytes. If this cannot be avoided, pass allow_inefficient=True to the " -"constructor" -msgstr "" - #: shared-bindings/rgbmatrix/RGBMatrix.c #, c-format msgid "Must use a multiple of 6 rgb pins, not %d" @@ -4271,10 +4423,6 @@ msgstr "" msgid "%q must be a subclass of %q" msgstr "" -#: shared-bindings/wifi/Monitor.c -msgid "%q out of bounds" -msgstr "%q je mimo hranice" - #: shared-bindings/wifi/Radio.c msgid "Invalid hex password" msgstr "Špatné heslo v hex" @@ -4345,7 +4493,7 @@ msgstr "Dekodér nelze přiřadit" msgid "Failed to parse MP3 file" msgstr "Soubor MP3 se nepodařilo analyzovat" -#: shared-module/bitbangio/I2C.c +#: shared-module/bitbangio/I2C.c shared-module/msgpack/__init__.c msgid "%q too long" msgstr "" @@ -4404,6 +4552,14 @@ msgstr "Nepodporovaná sběrnice dispalye" msgid "unsupported colorspace for GifWriter" msgstr "" +#: shared-module/hmac/HMAC.c +msgid "key does not support this digest" +msgstr "" + +#: shared-module/hmac/HMAC.c +msgid "HMAC of an empty message is not supported with a hardware key" +msgstr "" + #: shared-module/i2cdisplaybus/I2CDisplayBus.c #: shared-module/is31fl3741/IS31FL3741.c #, c-format @@ -4477,6 +4633,28 @@ msgid "" "requires contiguous pins." msgstr "" +#: shared-module/rgbmatrix/RGBMatrix.c +msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" +msgstr "Počet prvků rgb_pin musí být 6, 12, 18, 24, nebo 30" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] is not on the same port as clock" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] duplicates another pin assignment" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "" +"Pinout uses %d bytes per element, which consumes more than the ideal %d " +"bytes. If this cannot be avoided, pass allow_inefficient=True to the " +"constructor" +msgstr "" + #: shared-module/rgbmatrix/RGBMatrix.c msgid "No timer available" msgstr "Není k dispozici žádný časovač" @@ -4722,6 +4900,9 @@ msgstr "vypnuto" msgid "No IP" msgstr "Není IP" +#~ msgid "%q out of bounds" +#~ msgstr "%q je mimo hranice" + #~ msgid "asm overflow" #~ msgstr "přetečení v asm" diff --git a/locale/el.po b/locale/el.po index 5a4a07043a7..14ae1daf651 100644 --- a/locale/el.po +++ b/locale/el.po @@ -102,6 +102,10 @@ msgstr "" msgid "ndarray length overflows" msgstr "" +#: extmod/ulab/code/ndarray.c +msgid "maximum number of dimensions is " +msgstr "" + #: extmod/ulab/code/ndarray.c msgid "cannot convert complex type" msgstr "" @@ -156,11 +160,23 @@ msgid "operation is not supported for given type" msgstr "" #: extmod/ulab/code/ndarray.c -msgid "shape must be integer or tuple of integers" +msgid "keyword argument must be tuple of integers" msgstr "" -#: extmod/ulab/code/ndarray.c extmod/ulab/code/numpy/random/random.c -msgid "maximum number of dimensions is " +#: extmod/ulab/code/ndarray.c +msgid "too many axes specified" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "axis index out of bounds" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "repeated indices" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "shape must be integer or tuple of integers" msgstr "" #: extmod/ulab/code/ndarray.c @@ -227,6 +243,31 @@ msgstr "" msgid "not implemented for complex dtype" msgstr "" +#: extmod/ulab/code/numpy/compare.c extmod/ulab/code/user/user.c +#: shared-bindings/_eve/__init__.c +msgid "input must be an ndarray" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "object too deep for desired array" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast array data from dtype" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "minlength must not be negative" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "the weights and list don't have the same length" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast weigths to float" +msgstr "" + #: extmod/ulab/code/numpy/compare.c extmod/ulab/code/numpy/create.c #: extmod/ulab/code/numpy/io/io.c extmod/ulab/code/numpy/transform.c #: extmod/ulab/code/numpy/vector.c @@ -261,7 +302,7 @@ msgstr "" msgid "wrong axis specified" msgstr "" -#: extmod/ulab/code/numpy/create.c +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/utils/utils.c msgid "input arrays are not compatible" msgstr "" @@ -273,6 +314,45 @@ msgstr "" msgid "number of points must be at least 2" msgstr "" +#: extmod/ulab/code/numpy/create.c +msgid "indexing should be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "indexing must be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "too many input arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: extmod/ulab/code/numpy/transform.c +msgid "arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "arguments must be 1D arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "input is not an array" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: ports/espressif/common-hal/pulseio/PulseIn.c +#: shared-bindings/bitmaptools/__init__.c +msgid "index out of range" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "mode should be raise, wrap or clip" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "index must not be negative" +msgstr "" + #: extmod/ulab/code/numpy/create.c msgid "offset must be non-negative and no greater than buffer length" msgstr "" @@ -293,7 +373,7 @@ msgstr "" msgid "FFT is implemented for linear arrays only" msgstr "" -#: extmod/ulab/code/numpy/fft/fft_tools.c +#: extmod/ulab/code/numpy/fft/fft_tools.c extmod/ulab/code/utils/utils.c msgid "input array length must be power of 2" msgstr "" @@ -402,10 +482,6 @@ msgstr "" msgid "axis too long" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/numpy/transform.c -msgid "arguments must be ndarrays" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "cross is defined for 1D arrays of length 3" msgstr "" @@ -414,12 +490,6 @@ msgstr "" msgid "diff argument must be an ndarray" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/ulab_tools.c -#: ports/espressif/common-hal/pulseio/PulseIn.c -#: shared-bindings/bitmaptools/__init__.c -msgid "index out of range" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "differentiation order out of range" msgstr "" @@ -465,10 +535,10 @@ msgid "argument must be None, an integer or a tuple of integers" msgstr "" #: extmod/ulab/code/numpy/random/random.c -msgid "shape must be None, and integer or a tuple of integers" +msgid "shape must be None, an integer or a tuple of integers" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "out has wrong type" msgstr "" @@ -480,7 +550,7 @@ msgstr "" msgid "size must match out.shape when used together" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "output array must be contiguous" msgstr "" @@ -504,7 +574,7 @@ msgstr "" msgid "dimensions do not match" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/utils/utils.c msgid "out must be an ndarray" msgstr "" @@ -540,7 +610,7 @@ msgstr "" msgid "input dtype must be float or complex" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/scipy/integrate/integrate.c msgid "first argument must be a callable" msgstr "" @@ -548,18 +618,36 @@ msgstr "" msgid "wrong output type" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c -msgid "first two arguments must be ndarrays" +#: extmod/ulab/code/scipy/integrate/integrate.c +#, c-format +msgid "can't convert arg %d from %s to float" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c -msgid "input must be a dense ndarray" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "levels needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "steps needs to be a positive integer" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c #: extmod/ulab/code/scipy/optimize/optimize.c msgid "first argument must be a function" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "order needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c +msgid "first two arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c +msgid "input must be a dense ndarray" +msgstr "" + #: extmod/ulab/code/scipy/optimize/optimize.c msgid "function has the same sign at the ends of interval" msgstr "" @@ -624,8 +712,16 @@ msgstr "" msgid "input must be square matrix" msgstr "" -#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c -msgid "input must be an ndarray" +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dtype" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dimension" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong shape" msgstr "" #: extmod/ulab/code/utils/utils.c @@ -640,6 +736,34 @@ msgstr "" msgid "out array is too small" msgstr "" +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is defined for ndarrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is implemented for 1D arrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "out array must be a 1D array of float type" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "input and out arrays must have same length" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be an ndarray" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be a 1D dense float array" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be twice as long as input" +msgstr "" + #: extmod/vfs_fat.c py/moderrno.c msgid "Read-only filesystem" msgstr "" @@ -758,6 +882,7 @@ msgstr "" #: ports/analog/common-hal/busio/UART.c #: ports/atmel-samd/common-hal/busio/UART.c ports/cxd56/common-hal/busio/UART.c #: ports/nordic/common-hal/busio/UART.c ports/stm/common-hal/busio/UART.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "RS485" msgstr "RS485" @@ -770,7 +895,9 @@ msgstr "" msgid "Both RX and TX required for flow control" msgstr "Και RX και TX απαιτούνται για έλεγχο flow" -#: ports/analog/common-hal/busio/UART.c shared-module/rgbmatrix/RGBMatrix.c +#: ports/analog/common-hal/busio/UART.c +#: ports/raspberrypi/common-hal/usb_host/Port.c +#: shared-module/rgbmatrix/RGBMatrix.c msgid "Failed to allocate %q buffer" msgstr "" @@ -783,7 +910,7 @@ msgid "UART transaction timeout" msgstr "" #: ports/analog/common-hal/busio/UART.c ports/espressif/common-hal/busio/UART.c -#: ports/nordic/common-hal/busio/UART.c +#: ports/nordic/common-hal/busio/UART.c ports/zephyr-cp/common-hal/busio/UART.c msgid "All UART peripherals are in use" msgstr "Όλα τα UART περιφεριακά ειναι σε χρήση" @@ -857,6 +984,9 @@ msgstr "Όλοι οι χρονιστές βρίσκονται σε χρήση" #: ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c +#: ports/zephyr-cp/common-hal/busio/I2C.c +#: ports/zephyr-cp/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "Internal resource(s) in use" msgstr "" @@ -1024,6 +1154,21 @@ msgstr "Το Data 0 pin πρέπει να είναι byte aligned" msgid "Bus pin %d is already in use" msgstr "Bus pin %d είναι ήδη σε χρήση" +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/espressif/common-hal/espidf/__init__.c +#: ports/raspberrypi/common-hal/picogame/Display.c +#: shared-bindings/picogame/Canvas.c shared-bindings/picogame/__init__.c +msgid "Operation or feature not supported" +msgstr "" + +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c +#: shared-bindings/bitmapfilter/__init__.c +#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c +#: shared-module/synthio/__init__.c +msgid "%q must be of type %q, not %q" +msgstr "%q πρέπει να είναι τύπου %q, όχι %q" + #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nordic/common-hal/pulseio/PulseIn.c @@ -1191,11 +1336,6 @@ msgstr "Δεν υποστηρίζονται δεδομένα με κατευθυ msgid "Timeout is too long: Maximum timeout length is %d seconds" msgstr "" -#: ports/espressif/common-hal/_bleio/Characteristic.c -#: ports/espressif/common-hal/_bleio/Descriptor.c -msgid "MITM security not supported" -msgstr "" - #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Descriptor.c @@ -1210,6 +1350,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Service.c +#: ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c msgid "Too many %q" msgstr "" @@ -1224,6 +1365,20 @@ msgstr "" msgid "Can't set CCCD on local Characteristic" msgstr "Δεν μπορεί να οριστεί CCCD σε τοπικό Characteristic" +#: ports/espressif/common-hal/_bleio/Connection.c +#: ports/espressif/common-hal/_bleio/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c +#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c +#: shared-bindings/_bleio/CharacteristicBuffer.c +msgid "Not connected" +msgstr "" + +#: ports/espressif/common-hal/_bleio/Connection.c +msgid "No pairing in progress" +msgstr "" + #: ports/espressif/common-hal/_bleio/Connection.c #: ports/nordic/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" @@ -1257,15 +1412,6 @@ msgstr "" msgid "Invalid BLE parameter" msgstr "" -#: ports/espressif/common-hal/_bleio/__init__.c -#: ports/nordic/common-hal/_bleio/__init__.c -#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c -#: ports/zephyr-cp/common-hal/_bleio/Connection.c -#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c -#: shared-bindings/_bleio/CharacteristicBuffer.c -msgid "Not connected" -msgstr "" - #: ports/espressif/common-hal/_bleio/__init__.c #: shared-module/audiofilewriter/AudioFileWriter.c msgid "Already in progress" @@ -1392,7 +1538,7 @@ msgstr "" #: ports/espressif/common-hal/busio/I2C.c #: ports/espressif/common-hal/i2ctarget/I2CTarget.c -#: ports/nordic/common-hal/busio/I2C.c +#: ports/nordic/common-hal/busio/I2C.c ports/zephyr-cp/common-hal/busio/I2C.c msgid "All I2C peripherals are in use" msgstr "Όλα τα I2C περιφεριακά ειναι σε χρήση" @@ -1406,6 +1552,7 @@ msgid "SPI configuration failed" msgstr "" #: ports/espressif/common-hal/busio/SPI.c ports/nordic/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/SPI.c msgid "All SPI peripherals are in use" msgstr "Όλα τα SPI περιφεριακά είναι σε χρήση" @@ -1491,12 +1638,6 @@ msgstr "" msgid "Requested resource not found" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c -#: ports/raspberrypi/common-hal/picogame/Display.c -#: shared-bindings/picogame/__init__.c -msgid "Operation or feature not supported" -msgstr "" - #: ports/espressif/common-hal/espidf/__init__.c #: ports/espressif/common-hal/qspibus/QSPIBus.c msgid "Operation timed out" @@ -1992,13 +2133,6 @@ msgstr "" msgid "All timers for this pin are in use" msgstr "Όλοι οι χρονιστές για αυτό το pin χρησιμοποιούνται ήδη" -#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c -#: shared-bindings/bitmapfilter/__init__.c -#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c -#: shared-module/synthio/__init__.c -msgid "%q must be of type %q, not %q" -msgstr "%q πρέπει να είναι τύπου %q, όχι %q" - #: ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c msgid "Pins must be sequential GPIO pins" msgstr "" @@ -2419,6 +2553,10 @@ msgstr "" msgid "bad typecode" msgstr "" +#: py/binary.c +msgid "integer out of range" +msgstr "" + #: py/builtinevex.c msgid "bad compile mode" msgstr "" @@ -2537,6 +2675,10 @@ msgstr "" msgid "* arg after **" msgstr "* όρισμα μετά **" +#: py/compile.c +msgid "* arg after kwarg" +msgstr "" + #: py/compile.c msgid "too many args" msgstr "" @@ -2665,6 +2807,22 @@ msgstr "" msgid "opcode '%q' argument %d: must not be zero" msgstr "" +#: py/emitinlinerv32.c +msgid "opcode '%q': registers must be different" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': malformed register list" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': invalid stack adjustment" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q' argument %d: wrong register(s)" +msgstr "" + #: py/emitinlinerv32.c msgid "invalid RV32 instruction '%q'" msgstr "" @@ -2826,6 +2984,10 @@ msgstr "" msgid "return expected '%q' but got '%q'" msgstr "" +#: py/emitnative.c +msgid "native raise" +msgstr "" + #: py/emitnative.c msgid "must raise an object" msgstr "" @@ -2838,8 +3000,8 @@ msgstr "" msgid "unicode name escapes" msgstr "" -#: py/modbuiltins.c -msgid "chr() arg not in range(0x110000)" +#: py/modbuiltins.c py/objstr.c +msgid "char not in range(0x110000)" msgstr "" #: py/modbuiltins.c @@ -3143,11 +3305,6 @@ msgstr "" msgid "float too big" msgstr "" -#: py/objint.c -#, c-format -msgid "value must fit in %d byte(s)" -msgstr "" - #: py/objint.c shared-bindings/time/__init__.c msgid "No long integer support" msgstr "" @@ -3161,6 +3318,15 @@ msgstr "" msgid "%q=%q" msgstr "%q=%q" +#: py/objint_impl.h +#, c-format +msgid "value would overflow a %d byte buffer" +msgstr "" + +#: py/objint_impl.h +msgid "can't convert negative int to unsigned" +msgstr "" + #: py/objint_longlong.c py/parsenum.c msgid "result overflows long long storage" msgstr "" @@ -3201,6 +3367,10 @@ msgstr "%q βήμα δεν μπορεί να είναι μηδέν" msgid "Cannot subclass slice" msgstr "Δεν γίνεται υποκατηγορία ενός slice" +#: py/objstr.c +msgid "unknown encoding: %q" +msgstr "" + #: py/objstr.c msgid "bytes value out of range" msgstr "" @@ -3785,12 +3955,6 @@ msgstr "%q πρέπει να είναι bytearray ή array τύπου 'h', 'H', msgid "Length of %q must be an even multiple of channel_count * type_size" msgstr "" -#: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c -#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c -#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c -msgid "file must be a file opened in byte mode" -msgstr "" - #: shared-bindings/audiodelays/Chorus.c shared-bindings/audiodelays/Echo.c #: shared-bindings/audiodelays/Flanger.c #: shared-bindings/audiodelays/GranularPitchShift.c @@ -3819,6 +3983,12 @@ msgstr "" msgid "%q and %q must be different" msgstr "%q και %q πρεπει να είναι διαφορετικά" +#: shared-bindings/audiomp3/MP3Decoder.c +#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c +#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c +msgid "file must be a file opened in byte mode" +msgstr "" + #: shared-bindings/bitmapfilter/__init__.c msgid "" "weights must be a sequence with an odd square number of elements (usually 9 " @@ -4055,10 +4225,14 @@ msgstr "Το buffer δεν είναι ένα bytearray." msgid "System entry must be gnss.SatelliteSystem" msgstr "" -#: shared-bindings/hashlib/__init__.c +#: shared-bindings/hashlib/__init__.c shared-bindings/hmac/__init__.c msgid "Unsupported hash algorithm" msgstr "" +#: shared-bindings/hmac/__init__.c +msgid "hardware key slot is unused" +msgstr "" + #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr "" @@ -4170,28 +4344,6 @@ msgstr "" msgid "Publishers can only be created from a parent node" msgstr "" -#: shared-bindings/rgbmatrix/RGBMatrix.c -msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] is not on the same port as clock" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] duplicates another pin assignment" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "" -"Pinout uses %d bytes per element, which consumes more than the ideal %d " -"bytes. If this cannot be avoided, pass allow_inefficient=True to the " -"constructor" -msgstr "" - #: shared-bindings/rgbmatrix/RGBMatrix.c #, c-format msgid "Must use a multiple of 6 rgb pins, not %d" @@ -4271,10 +4423,6 @@ msgstr "" msgid "%q must be a subclass of %q" msgstr "%q πρέπει να είναι υποκλάση του %q" -#: shared-bindings/wifi/Monitor.c -msgid "%q out of bounds" -msgstr "%q εκτός ορίων" - #: shared-bindings/wifi/Radio.c msgid "Invalid hex password" msgstr "" @@ -4345,7 +4493,7 @@ msgstr "Δεν μπόρεσε να δεσμευτεί decoder" msgid "Failed to parse MP3 file" msgstr "" -#: shared-module/bitbangio/I2C.c +#: shared-module/bitbangio/I2C.c shared-module/msgpack/__init__.c msgid "%q too long" msgstr "" @@ -4403,6 +4551,14 @@ msgstr "" msgid "unsupported colorspace for GifWriter" msgstr "" +#: shared-module/hmac/HMAC.c +msgid "key does not support this digest" +msgstr "" + +#: shared-module/hmac/HMAC.c +msgid "HMAC of an empty message is not supported with a hardware key" +msgstr "" + #: shared-module/i2cdisplaybus/I2CDisplayBus.c #: shared-module/is31fl3741/IS31FL3741.c #, c-format @@ -4476,6 +4632,28 @@ msgid "" "requires contiguous pins." msgstr "" +#: shared-module/rgbmatrix/RGBMatrix.c +msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] is not on the same port as clock" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] duplicates another pin assignment" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "" +"Pinout uses %d bytes per element, which consumes more than the ideal %d " +"bytes. If this cannot be avoided, pass allow_inefficient=True to the " +"constructor" +msgstr "" + #: shared-module/rgbmatrix/RGBMatrix.c msgid "No timer available" msgstr "" @@ -4721,6 +4899,9 @@ msgstr "" msgid "No IP" msgstr "" +#~ msgid "%q out of bounds" +#~ msgstr "%q εκτός ορίων" + #~ msgid "%q renamed %q" #~ msgstr "%q μεταονομάστηκε σε %q" diff --git a/locale/hi.po b/locale/hi.po index 1f81934b26e..7a0f55eac6d 100644 --- a/locale/hi.po +++ b/locale/hi.po @@ -102,6 +102,10 @@ msgstr "" msgid "ndarray length overflows" msgstr "" +#: extmod/ulab/code/ndarray.c +msgid "maximum number of dimensions is " +msgstr "" + #: extmod/ulab/code/ndarray.c msgid "cannot convert complex type" msgstr "" @@ -156,11 +160,23 @@ msgid "operation is not supported for given type" msgstr "" #: extmod/ulab/code/ndarray.c -msgid "shape must be integer or tuple of integers" +msgid "keyword argument must be tuple of integers" msgstr "" -#: extmod/ulab/code/ndarray.c extmod/ulab/code/numpy/random/random.c -msgid "maximum number of dimensions is " +#: extmod/ulab/code/ndarray.c +msgid "too many axes specified" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "axis index out of bounds" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "repeated indices" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "shape must be integer or tuple of integers" msgstr "" #: extmod/ulab/code/ndarray.c @@ -227,6 +243,31 @@ msgstr "" msgid "not implemented for complex dtype" msgstr "" +#: extmod/ulab/code/numpy/compare.c extmod/ulab/code/user/user.c +#: shared-bindings/_eve/__init__.c +msgid "input must be an ndarray" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "object too deep for desired array" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast array data from dtype" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "minlength must not be negative" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "the weights and list don't have the same length" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast weigths to float" +msgstr "" + #: extmod/ulab/code/numpy/compare.c extmod/ulab/code/numpy/create.c #: extmod/ulab/code/numpy/io/io.c extmod/ulab/code/numpy/transform.c #: extmod/ulab/code/numpy/vector.c @@ -261,7 +302,7 @@ msgstr "" msgid "wrong axis specified" msgstr "" -#: extmod/ulab/code/numpy/create.c +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/utils/utils.c msgid "input arrays are not compatible" msgstr "" @@ -273,6 +314,45 @@ msgstr "" msgid "number of points must be at least 2" msgstr "" +#: extmod/ulab/code/numpy/create.c +msgid "indexing should be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "indexing must be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "too many input arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: extmod/ulab/code/numpy/transform.c +msgid "arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "arguments must be 1D arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "input is not an array" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: ports/espressif/common-hal/pulseio/PulseIn.c +#: shared-bindings/bitmaptools/__init__.c +msgid "index out of range" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "mode should be raise, wrap or clip" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "index must not be negative" +msgstr "" + #: extmod/ulab/code/numpy/create.c msgid "offset must be non-negative and no greater than buffer length" msgstr "" @@ -293,7 +373,7 @@ msgstr "" msgid "FFT is implemented for linear arrays only" msgstr "" -#: extmod/ulab/code/numpy/fft/fft_tools.c +#: extmod/ulab/code/numpy/fft/fft_tools.c extmod/ulab/code/utils/utils.c msgid "input array length must be power of 2" msgstr "" @@ -402,10 +482,6 @@ msgstr "" msgid "axis too long" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/numpy/transform.c -msgid "arguments must be ndarrays" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "cross is defined for 1D arrays of length 3" msgstr "" @@ -414,12 +490,6 @@ msgstr "" msgid "diff argument must be an ndarray" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/ulab_tools.c -#: ports/espressif/common-hal/pulseio/PulseIn.c -#: shared-bindings/bitmaptools/__init__.c -msgid "index out of range" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "differentiation order out of range" msgstr "" @@ -465,10 +535,10 @@ msgid "argument must be None, an integer or a tuple of integers" msgstr "" #: extmod/ulab/code/numpy/random/random.c -msgid "shape must be None, and integer or a tuple of integers" +msgid "shape must be None, an integer or a tuple of integers" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "out has wrong type" msgstr "" @@ -480,7 +550,7 @@ msgstr "" msgid "size must match out.shape when used together" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "output array must be contiguous" msgstr "" @@ -504,7 +574,7 @@ msgstr "" msgid "dimensions do not match" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/utils/utils.c msgid "out must be an ndarray" msgstr "" @@ -540,7 +610,7 @@ msgstr "" msgid "input dtype must be float or complex" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/scipy/integrate/integrate.c msgid "first argument must be a callable" msgstr "" @@ -548,18 +618,36 @@ msgstr "" msgid "wrong output type" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c -msgid "first two arguments must be ndarrays" +#: extmod/ulab/code/scipy/integrate/integrate.c +#, c-format +msgid "can't convert arg %d from %s to float" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c -msgid "input must be a dense ndarray" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "levels needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "steps needs to be a positive integer" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c #: extmod/ulab/code/scipy/optimize/optimize.c msgid "first argument must be a function" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "order needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c +msgid "first two arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c +msgid "input must be a dense ndarray" +msgstr "" + #: extmod/ulab/code/scipy/optimize/optimize.c msgid "function has the same sign at the ends of interval" msgstr "" @@ -624,8 +712,16 @@ msgstr "" msgid "input must be square matrix" msgstr "" -#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c -msgid "input must be an ndarray" +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dtype" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dimension" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong shape" msgstr "" #: extmod/ulab/code/utils/utils.c @@ -640,6 +736,34 @@ msgstr "" msgid "out array is too small" msgstr "" +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is defined for ndarrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is implemented for 1D arrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "out array must be a 1D array of float type" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "input and out arrays must have same length" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be an ndarray" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be a 1D dense float array" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be twice as long as input" +msgstr "" + #: extmod/vfs_fat.c py/moderrno.c msgid "Read-only filesystem" msgstr "" @@ -749,6 +873,7 @@ msgstr "" #: ports/analog/common-hal/busio/UART.c #: ports/atmel-samd/common-hal/busio/UART.c ports/cxd56/common-hal/busio/UART.c #: ports/nordic/common-hal/busio/UART.c ports/stm/common-hal/busio/UART.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "RS485" msgstr "RS485" @@ -761,7 +886,9 @@ msgstr "" msgid "Both RX and TX required for flow control" msgstr "" -#: ports/analog/common-hal/busio/UART.c shared-module/rgbmatrix/RGBMatrix.c +#: ports/analog/common-hal/busio/UART.c +#: ports/raspberrypi/common-hal/usb_host/Port.c +#: shared-module/rgbmatrix/RGBMatrix.c msgid "Failed to allocate %q buffer" msgstr "" @@ -774,7 +901,7 @@ msgid "UART transaction timeout" msgstr "" #: ports/analog/common-hal/busio/UART.c ports/espressif/common-hal/busio/UART.c -#: ports/nordic/common-hal/busio/UART.c +#: ports/nordic/common-hal/busio/UART.c ports/zephyr-cp/common-hal/busio/UART.c msgid "All UART peripherals are in use" msgstr "" @@ -848,6 +975,9 @@ msgstr "" #: ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c +#: ports/zephyr-cp/common-hal/busio/I2C.c +#: ports/zephyr-cp/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "Internal resource(s) in use" msgstr "" @@ -1015,6 +1145,21 @@ msgstr "" msgid "Bus pin %d is already in use" msgstr "" +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/espressif/common-hal/espidf/__init__.c +#: ports/raspberrypi/common-hal/picogame/Display.c +#: shared-bindings/picogame/Canvas.c shared-bindings/picogame/__init__.c +msgid "Operation or feature not supported" +msgstr "" + +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c +#: shared-bindings/bitmapfilter/__init__.c +#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c +#: shared-module/synthio/__init__.c +msgid "%q must be of type %q, not %q" +msgstr "" + #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nordic/common-hal/pulseio/PulseIn.c @@ -1182,11 +1327,6 @@ msgstr "" msgid "Timeout is too long: Maximum timeout length is %d seconds" msgstr "" -#: ports/espressif/common-hal/_bleio/Characteristic.c -#: ports/espressif/common-hal/_bleio/Descriptor.c -msgid "MITM security not supported" -msgstr "" - #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Descriptor.c @@ -1201,6 +1341,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Service.c +#: ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c msgid "Too many %q" msgstr "" @@ -1215,6 +1356,20 @@ msgstr "" msgid "Can't set CCCD on local Characteristic" msgstr "" +#: ports/espressif/common-hal/_bleio/Connection.c +#: ports/espressif/common-hal/_bleio/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c +#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c +#: shared-bindings/_bleio/CharacteristicBuffer.c +msgid "Not connected" +msgstr "" + +#: ports/espressif/common-hal/_bleio/Connection.c +msgid "No pairing in progress" +msgstr "" + #: ports/espressif/common-hal/_bleio/Connection.c #: ports/nordic/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" @@ -1248,15 +1403,6 @@ msgstr "" msgid "Invalid BLE parameter" msgstr "" -#: ports/espressif/common-hal/_bleio/__init__.c -#: ports/nordic/common-hal/_bleio/__init__.c -#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c -#: ports/zephyr-cp/common-hal/_bleio/Connection.c -#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c -#: shared-bindings/_bleio/CharacteristicBuffer.c -msgid "Not connected" -msgstr "" - #: ports/espressif/common-hal/_bleio/__init__.c #: shared-module/audiofilewriter/AudioFileWriter.c msgid "Already in progress" @@ -1382,7 +1528,7 @@ msgstr "" #: ports/espressif/common-hal/busio/I2C.c #: ports/espressif/common-hal/i2ctarget/I2CTarget.c -#: ports/nordic/common-hal/busio/I2C.c +#: ports/nordic/common-hal/busio/I2C.c ports/zephyr-cp/common-hal/busio/I2C.c msgid "All I2C peripherals are in use" msgstr "" @@ -1396,6 +1542,7 @@ msgid "SPI configuration failed" msgstr "" #: ports/espressif/common-hal/busio/SPI.c ports/nordic/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/SPI.c msgid "All SPI peripherals are in use" msgstr "" @@ -1481,12 +1628,6 @@ msgstr "" msgid "Requested resource not found" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c -#: ports/raspberrypi/common-hal/picogame/Display.c -#: shared-bindings/picogame/__init__.c -msgid "Operation or feature not supported" -msgstr "" - #: ports/espressif/common-hal/espidf/__init__.c #: ports/espressif/common-hal/qspibus/QSPIBus.c msgid "Operation timed out" @@ -1982,13 +2123,6 @@ msgstr "" msgid "All timers for this pin are in use" msgstr "" -#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c -#: shared-bindings/bitmapfilter/__init__.c -#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c -#: shared-module/synthio/__init__.c -msgid "%q must be of type %q, not %q" -msgstr "" - #: ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c msgid "Pins must be sequential GPIO pins" msgstr "" @@ -2409,6 +2543,10 @@ msgstr "" msgid "bad typecode" msgstr "" +#: py/binary.c +msgid "integer out of range" +msgstr "" + #: py/builtinevex.c msgid "bad compile mode" msgstr "" @@ -2527,6 +2665,10 @@ msgstr "" msgid "* arg after **" msgstr "" +#: py/compile.c +msgid "* arg after kwarg" +msgstr "" + #: py/compile.c msgid "too many args" msgstr "" @@ -2655,6 +2797,22 @@ msgstr "" msgid "opcode '%q' argument %d: must not be zero" msgstr "" +#: py/emitinlinerv32.c +msgid "opcode '%q': registers must be different" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': malformed register list" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': invalid stack adjustment" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q' argument %d: wrong register(s)" +msgstr "" + #: py/emitinlinerv32.c msgid "invalid RV32 instruction '%q'" msgstr "" @@ -2816,6 +2974,10 @@ msgstr "" msgid "return expected '%q' but got '%q'" msgstr "" +#: py/emitnative.c +msgid "native raise" +msgstr "" + #: py/emitnative.c msgid "must raise an object" msgstr "" @@ -2828,8 +2990,8 @@ msgstr "" msgid "unicode name escapes" msgstr "" -#: py/modbuiltins.c -msgid "chr() arg not in range(0x110000)" +#: py/modbuiltins.c py/objstr.c +msgid "char not in range(0x110000)" msgstr "" #: py/modbuiltins.c @@ -3132,11 +3294,6 @@ msgstr "" msgid "float too big" msgstr "" -#: py/objint.c -#, c-format -msgid "value must fit in %d byte(s)" -msgstr "" - #: py/objint.c shared-bindings/time/__init__.c msgid "No long integer support" msgstr "" @@ -3150,6 +3307,15 @@ msgstr "" msgid "%q=%q" msgstr "" +#: py/objint_impl.h +#, c-format +msgid "value would overflow a %d byte buffer" +msgstr "" + +#: py/objint_impl.h +msgid "can't convert negative int to unsigned" +msgstr "" + #: py/objint_longlong.c py/parsenum.c msgid "result overflows long long storage" msgstr "" @@ -3190,6 +3356,10 @@ msgstr "" msgid "Cannot subclass slice" msgstr "" +#: py/objstr.c +msgid "unknown encoding: %q" +msgstr "" + #: py/objstr.c msgid "bytes value out of range" msgstr "" @@ -3768,12 +3938,6 @@ msgstr "" msgid "Length of %q must be an even multiple of channel_count * type_size" msgstr "" -#: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c -#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c -#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c -msgid "file must be a file opened in byte mode" -msgstr "" - #: shared-bindings/audiodelays/Chorus.c shared-bindings/audiodelays/Echo.c #: shared-bindings/audiodelays/Flanger.c #: shared-bindings/audiodelays/GranularPitchShift.c @@ -3802,6 +3966,12 @@ msgstr "" msgid "%q and %q must be different" msgstr "" +#: shared-bindings/audiomp3/MP3Decoder.c +#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c +#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c +msgid "file must be a file opened in byte mode" +msgstr "" + #: shared-bindings/bitmapfilter/__init__.c msgid "" "weights must be a sequence with an odd square number of elements (usually 9 " @@ -4037,10 +4207,14 @@ msgstr "" msgid "System entry must be gnss.SatelliteSystem" msgstr "" -#: shared-bindings/hashlib/__init__.c +#: shared-bindings/hashlib/__init__.c shared-bindings/hmac/__init__.c msgid "Unsupported hash algorithm" msgstr "" +#: shared-bindings/hmac/__init__.c +msgid "hardware key slot is unused" +msgstr "" + #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr "" @@ -4152,28 +4326,6 @@ msgstr "" msgid "Publishers can only be created from a parent node" msgstr "" -#: shared-bindings/rgbmatrix/RGBMatrix.c -msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] is not on the same port as clock" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] duplicates another pin assignment" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "" -"Pinout uses %d bytes per element, which consumes more than the ideal %d " -"bytes. If this cannot be avoided, pass allow_inefficient=True to the " -"constructor" -msgstr "" - #: shared-bindings/rgbmatrix/RGBMatrix.c #, c-format msgid "Must use a multiple of 6 rgb pins, not %d" @@ -4252,10 +4404,6 @@ msgstr "" msgid "%q must be a subclass of %q" msgstr "" -#: shared-bindings/wifi/Monitor.c -msgid "%q out of bounds" -msgstr "" - #: shared-bindings/wifi/Radio.c msgid "Invalid hex password" msgstr "" @@ -4326,7 +4474,7 @@ msgstr "" msgid "Failed to parse MP3 file" msgstr "" -#: shared-module/bitbangio/I2C.c +#: shared-module/bitbangio/I2C.c shared-module/msgpack/__init__.c msgid "%q too long" msgstr "" @@ -4384,6 +4532,14 @@ msgstr "" msgid "unsupported colorspace for GifWriter" msgstr "" +#: shared-module/hmac/HMAC.c +msgid "key does not support this digest" +msgstr "" + +#: shared-module/hmac/HMAC.c +msgid "HMAC of an empty message is not supported with a hardware key" +msgstr "" + #: shared-module/i2cdisplaybus/I2CDisplayBus.c #: shared-module/is31fl3741/IS31FL3741.c #, c-format @@ -4457,6 +4613,28 @@ msgid "" "requires contiguous pins." msgstr "" +#: shared-module/rgbmatrix/RGBMatrix.c +msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] is not on the same port as clock" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] duplicates another pin assignment" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "" +"Pinout uses %d bytes per element, which consumes more than the ideal %d " +"bytes. If this cannot be avoided, pass allow_inefficient=True to the " +"constructor" +msgstr "" + #: shared-module/rgbmatrix/RGBMatrix.c msgid "No timer available" msgstr "" diff --git a/locale/ko.po b/locale/ko.po index be1dce33852..d31a4aed2d2 100644 --- a/locale/ko.po +++ b/locale/ko.po @@ -101,6 +101,10 @@ msgstr "" msgid "ndarray length overflows" msgstr "" +#: extmod/ulab/code/ndarray.c +msgid "maximum number of dimensions is " +msgstr "" + #: extmod/ulab/code/ndarray.c msgid "cannot convert complex type" msgstr "" @@ -155,11 +159,23 @@ msgid "operation is not supported for given type" msgstr "" #: extmod/ulab/code/ndarray.c -msgid "shape must be integer or tuple of integers" +msgid "keyword argument must be tuple of integers" msgstr "" -#: extmod/ulab/code/ndarray.c extmod/ulab/code/numpy/random/random.c -msgid "maximum number of dimensions is " +#: extmod/ulab/code/ndarray.c +msgid "too many axes specified" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "axis index out of bounds" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "repeated indices" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "shape must be integer or tuple of integers" msgstr "" #: extmod/ulab/code/ndarray.c @@ -226,6 +242,31 @@ msgstr "" msgid "not implemented for complex dtype" msgstr "" +#: extmod/ulab/code/numpy/compare.c extmod/ulab/code/user/user.c +#: shared-bindings/_eve/__init__.c +msgid "input must be an ndarray" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "object too deep for desired array" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast array data from dtype" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "minlength must not be negative" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "the weights and list don't have the same length" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast weigths to float" +msgstr "" + #: extmod/ulab/code/numpy/compare.c extmod/ulab/code/numpy/create.c #: extmod/ulab/code/numpy/io/io.c extmod/ulab/code/numpy/transform.c #: extmod/ulab/code/numpy/vector.c @@ -260,7 +301,7 @@ msgstr "" msgid "wrong axis specified" msgstr "" -#: extmod/ulab/code/numpy/create.c +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/utils/utils.c msgid "input arrays are not compatible" msgstr "" @@ -272,6 +313,45 @@ msgstr "" msgid "number of points must be at least 2" msgstr "" +#: extmod/ulab/code/numpy/create.c +msgid "indexing should be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "indexing must be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "too many input arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: extmod/ulab/code/numpy/transform.c +msgid "arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "arguments must be 1D arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "input is not an array" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: ports/espressif/common-hal/pulseio/PulseIn.c +#: shared-bindings/bitmaptools/__init__.c +msgid "index out of range" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "mode should be raise, wrap or clip" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "index must not be negative" +msgstr "" + #: extmod/ulab/code/numpy/create.c msgid "offset must be non-negative and no greater than buffer length" msgstr "" @@ -292,7 +372,7 @@ msgstr "FFT는 ndarrays에 대해서만 정의됩니다" msgid "FFT is implemented for linear arrays only" msgstr "FFT는 선형 배열에 대해서만 구현됩니다" -#: extmod/ulab/code/numpy/fft/fft_tools.c +#: extmod/ulab/code/numpy/fft/fft_tools.c extmod/ulab/code/utils/utils.c msgid "input array length must be power of 2" msgstr "" @@ -401,10 +481,6 @@ msgstr "" msgid "axis too long" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/numpy/transform.c -msgid "arguments must be ndarrays" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "cross is defined for 1D arrays of length 3" msgstr "" @@ -413,12 +489,6 @@ msgstr "" msgid "diff argument must be an ndarray" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/ulab_tools.c -#: ports/espressif/common-hal/pulseio/PulseIn.c -#: shared-bindings/bitmaptools/__init__.c -msgid "index out of range" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "differentiation order out of range" msgstr "" @@ -464,10 +534,10 @@ msgid "argument must be None, an integer or a tuple of integers" msgstr "" #: extmod/ulab/code/numpy/random/random.c -msgid "shape must be None, and integer or a tuple of integers" +msgid "shape must be None, an integer or a tuple of integers" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "out has wrong type" msgstr "" @@ -479,7 +549,7 @@ msgstr "" msgid "size must match out.shape when used together" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "output array must be contiguous" msgstr "" @@ -503,7 +573,7 @@ msgstr "" msgid "dimensions do not match" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/utils/utils.c msgid "out must be an ndarray" msgstr "" @@ -539,7 +609,7 @@ msgstr "" msgid "input dtype must be float or complex" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/scipy/integrate/integrate.c msgid "first argument must be a callable" msgstr "" @@ -547,18 +617,36 @@ msgstr "" msgid "wrong output type" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c -msgid "first two arguments must be ndarrays" +#: extmod/ulab/code/scipy/integrate/integrate.c +#, c-format +msgid "can't convert arg %d from %s to float" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c -msgid "input must be a dense ndarray" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "levels needs to be a positive integer" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "steps needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c #: extmod/ulab/code/scipy/optimize/optimize.c msgid "first argument must be a function" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "order needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c +msgid "first two arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c +msgid "input must be a dense ndarray" +msgstr "" + #: extmod/ulab/code/scipy/optimize/optimize.c msgid "function has the same sign at the ends of interval" msgstr "" @@ -623,8 +711,16 @@ msgstr "" msgid "input must be square matrix" msgstr "" -#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c -msgid "input must be an ndarray" +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dtype" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dimension" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong shape" msgstr "" #: extmod/ulab/code/utils/utils.c @@ -639,6 +735,34 @@ msgstr "" msgid "out array is too small" msgstr "" +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is defined for ndarrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is implemented for 1D arrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "out array must be a 1D array of float type" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "input and out arrays must have same length" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be an ndarray" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be a 1D dense float array" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be twice as long as input" +msgstr "" + #: extmod/vfs_fat.c py/moderrno.c msgid "Read-only filesystem" msgstr "읽기 전용 파일 시스템" @@ -755,6 +879,7 @@ msgstr "" #: ports/analog/common-hal/busio/UART.c #: ports/atmel-samd/common-hal/busio/UART.c ports/cxd56/common-hal/busio/UART.c #: ports/nordic/common-hal/busio/UART.c ports/stm/common-hal/busio/UART.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "RS485" msgstr "RS485" @@ -767,7 +892,9 @@ msgstr "" msgid "Both RX and TX required for flow control" msgstr "플로우 제어에 RX와 TX가 모두 필요합니다" -#: ports/analog/common-hal/busio/UART.c shared-module/rgbmatrix/RGBMatrix.c +#: ports/analog/common-hal/busio/UART.c +#: ports/raspberrypi/common-hal/usb_host/Port.c +#: shared-module/rgbmatrix/RGBMatrix.c msgid "Failed to allocate %q buffer" msgstr "%q 버퍼 할당에 실패했습니다" @@ -780,7 +907,7 @@ msgid "UART transaction timeout" msgstr "" #: ports/analog/common-hal/busio/UART.c ports/espressif/common-hal/busio/UART.c -#: ports/nordic/common-hal/busio/UART.c +#: ports/nordic/common-hal/busio/UART.c ports/zephyr-cp/common-hal/busio/UART.c msgid "All UART peripherals are in use" msgstr "사용중인 모든 UART주변 기기" @@ -855,6 +982,9 @@ msgstr "모든 타이머가 사용 중입니다" #: ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c +#: ports/zephyr-cp/common-hal/busio/I2C.c +#: ports/zephyr-cp/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "Internal resource(s) in use" msgstr "" @@ -1025,6 +1155,21 @@ msgstr "데이터 0 핀은 바이트 정렬되어야 합니다" msgid "Bus pin %d is already in use" msgstr "Bus 핀 %d은 이미 사용 중입니다" +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/espressif/common-hal/espidf/__init__.c +#: ports/raspberrypi/common-hal/picogame/Display.c +#: shared-bindings/picogame/Canvas.c shared-bindings/picogame/__init__.c +msgid "Operation or feature not supported" +msgstr "작업 또는 기능이 지원되지 않습니다" + +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c +#: shared-bindings/bitmapfilter/__init__.c +#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c +#: shared-module/synthio/__init__.c +msgid "%q must be of type %q, not %q" +msgstr "%q는 %q가 아니라 %q 유형이어야 합니다" + #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nordic/common-hal/pulseio/PulseIn.c @@ -1195,11 +1340,6 @@ msgstr "직접 광고에서는 데이터가 지원되지 않습니다" msgid "Timeout is too long: Maximum timeout length is %d seconds" msgstr "" -#: ports/espressif/common-hal/_bleio/Characteristic.c -#: ports/espressif/common-hal/_bleio/Descriptor.c -msgid "MITM security not supported" -msgstr "" - #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Descriptor.c @@ -1214,6 +1354,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Service.c +#: ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c msgid "Too many %q" msgstr "" @@ -1228,6 +1369,20 @@ msgstr "이 특성에 대한 CCCD가 없습니다" msgid "Can't set CCCD on local Characteristic" msgstr "로컬 특성에 CCCD를 설정할 수 없습니다" +#: ports/espressif/common-hal/_bleio/Connection.c +#: ports/espressif/common-hal/_bleio/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c +#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c +#: shared-bindings/_bleio/CharacteristicBuffer.c +msgid "Not connected" +msgstr "연결되지 않았습니다" + +#: ports/espressif/common-hal/_bleio/Connection.c +msgid "No pairing in progress" +msgstr "" + #: ports/espressif/common-hal/_bleio/Connection.c #: ports/nordic/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" @@ -1262,15 +1417,6 @@ msgstr "빠른 메모리 부족" msgid "Invalid BLE parameter" msgstr "잘못된 BLE 파라미터" -#: ports/espressif/common-hal/_bleio/__init__.c -#: ports/nordic/common-hal/_bleio/__init__.c -#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c -#: ports/zephyr-cp/common-hal/_bleio/Connection.c -#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c -#: shared-bindings/_bleio/CharacteristicBuffer.c -msgid "Not connected" -msgstr "연결되지 않았습니다" - #: ports/espressif/common-hal/_bleio/__init__.c #: shared-module/audiofilewriter/AudioFileWriter.c msgid "Already in progress" @@ -1399,7 +1545,7 @@ msgstr "" #: ports/espressif/common-hal/busio/I2C.c #: ports/espressif/common-hal/i2ctarget/I2CTarget.c -#: ports/nordic/common-hal/busio/I2C.c +#: ports/nordic/common-hal/busio/I2C.c ports/zephyr-cp/common-hal/busio/I2C.c msgid "All I2C peripherals are in use" msgstr "사용 중인 모든 I2C주변 기기" @@ -1413,6 +1559,7 @@ msgid "SPI configuration failed" msgstr "" #: ports/espressif/common-hal/busio/SPI.c ports/nordic/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/SPI.c msgid "All SPI peripherals are in use" msgstr "사용중인 모든 SPI주변 기기" @@ -1500,12 +1647,6 @@ msgstr "잘못된 크기" msgid "Requested resource not found" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c -#: ports/raspberrypi/common-hal/picogame/Display.c -#: shared-bindings/picogame/__init__.c -msgid "Operation or feature not supported" -msgstr "작업 또는 기능이 지원되지 않습니다" - #: ports/espressif/common-hal/espidf/__init__.c #: ports/espressif/common-hal/qspibus/QSPIBus.c #, fuzzy @@ -2011,13 +2152,6 @@ msgstr "" msgid "All timers for this pin are in use" msgstr "핀의 모든 타이머가 사용 중입니다" -#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c -#: shared-bindings/bitmapfilter/__init__.c -#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c -#: shared-module/synthio/__init__.c -msgid "%q must be of type %q, not %q" -msgstr "%q는 %q가 아니라 %q 유형이어야 합니다" - #: ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c msgid "Pins must be sequential GPIO pins" msgstr "핀은 순차적인 GPIO 핀이어야 합니다" @@ -2453,6 +2587,10 @@ msgstr "" msgid "bad typecode" msgstr "" +#: py/binary.c +msgid "integer out of range" +msgstr "" + #: py/builtinevex.c msgid "bad compile mode" msgstr "" @@ -2573,6 +2711,10 @@ msgstr "" msgid "* arg after **" msgstr "* 인수 뒤에 **" +#: py/compile.c +msgid "* arg after kwarg" +msgstr "" + #: py/compile.c msgid "too many args" msgstr "" @@ -2703,6 +2845,22 @@ msgstr "" msgid "opcode '%q' argument %d: must not be zero" msgstr "" +#: py/emitinlinerv32.c +msgid "opcode '%q': registers must be different" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': malformed register list" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': invalid stack adjustment" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q' argument %d: wrong register(s)" +msgstr "" + #: py/emitinlinerv32.c msgid "invalid RV32 instruction '%q'" msgstr "" @@ -2864,6 +3022,10 @@ msgstr "" msgid "return expected '%q' but got '%q'" msgstr "" +#: py/emitnative.c +msgid "native raise" +msgstr "" + #: py/emitnative.c msgid "must raise an object" msgstr "" @@ -2876,8 +3038,8 @@ msgstr "" msgid "unicode name escapes" msgstr "" -#: py/modbuiltins.c -msgid "chr() arg not in range(0x110000)" +#: py/modbuiltins.c py/objstr.c +msgid "char not in range(0x110000)" msgstr "" #: py/modbuiltins.c @@ -3181,11 +3343,6 @@ msgstr "" msgid "float too big" msgstr "float이 너무 큽니다" -#: py/objint.c -#, c-format -msgid "value must fit in %d byte(s)" -msgstr "" - #: py/objint.c shared-bindings/time/__init__.c msgid "No long integer support" msgstr "긴 정수 지원이 없습니다" @@ -3199,6 +3356,15 @@ msgstr "" msgid "%q=%q" msgstr "%q=%q" +#: py/objint_impl.h +#, c-format +msgid "value would overflow a %d byte buffer" +msgstr "" + +#: py/objint_impl.h +msgid "can't convert negative int to unsigned" +msgstr "" + #: py/objint_longlong.c py/parsenum.c msgid "result overflows long long storage" msgstr "" @@ -3240,6 +3406,10 @@ msgstr "%q 단계는 0일 수 없습니다" msgid "Cannot subclass slice" msgstr "" +#: py/objstr.c +msgid "unknown encoding: %q" +msgstr "" + #: py/objstr.c msgid "bytes value out of range" msgstr "" @@ -3824,12 +3994,6 @@ msgstr "%q는 h, H, b 또는 B 유형의 바이트 배열 또는 배열이어야 msgid "Length of %q must be an even multiple of channel_count * type_size" msgstr "" -#: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c -#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c -#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c -msgid "file must be a file opened in byte mode" -msgstr "" - #: shared-bindings/audiodelays/Chorus.c shared-bindings/audiodelays/Echo.c #: shared-bindings/audiodelays/Flanger.c #: shared-bindings/audiodelays/GranularPitchShift.c @@ -3858,6 +4022,12 @@ msgstr "" msgid "%q and %q must be different" msgstr "%q와 %q는 달라야 합니다" +#: shared-bindings/audiomp3/MP3Decoder.c +#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c +#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c +msgid "file must be a file opened in byte mode" +msgstr "" + #: shared-bindings/bitmapfilter/__init__.c msgid "" "weights must be a sequence with an odd square number of elements (usually 9 " @@ -4094,10 +4264,14 @@ msgstr "버퍼는 바이트 배열이 아닙니다." msgid "System entry must be gnss.SatelliteSystem" msgstr "" -#: shared-bindings/hashlib/__init__.c +#: shared-bindings/hashlib/__init__.c shared-bindings/hmac/__init__.c msgid "Unsupported hash algorithm" msgstr "" +#: shared-bindings/hmac/__init__.c +msgid "hardware key slot is unused" +msgstr "" + #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr "" @@ -4211,28 +4385,6 @@ msgstr "" msgid "Publishers can only be created from a parent node" msgstr "" -#: shared-bindings/rgbmatrix/RGBMatrix.c -msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] is not on the same port as clock" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] duplicates another pin assignment" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "" -"Pinout uses %d bytes per element, which consumes more than the ideal %d " -"bytes. If this cannot be avoided, pass allow_inefficient=True to the " -"constructor" -msgstr "" - #: shared-bindings/rgbmatrix/RGBMatrix.c #, c-format msgid "Must use a multiple of 6 rgb pins, not %d" @@ -4313,11 +4465,6 @@ msgstr "" msgid "%q must be a subclass of %q" msgstr "%q는 %q의 하위 클래스여야 합니다" -#: shared-bindings/wifi/Monitor.c -#, fuzzy -msgid "%q out of bounds" -msgstr "%q가 경계를 벗어남" - #: shared-bindings/wifi/Radio.c msgid "Invalid hex password" msgstr "잘못된 16진수 패스워드" @@ -4388,7 +4535,7 @@ msgstr "디코더를 할당할 수 없습니다" msgid "Failed to parse MP3 file" msgstr "MP3 파일 분석에 실패했습니다" -#: shared-module/bitbangio/I2C.c +#: shared-module/bitbangio/I2C.c shared-module/msgpack/__init__.c msgid "%q too long" msgstr "" @@ -4447,6 +4594,14 @@ msgstr "" msgid "unsupported colorspace for GifWriter" msgstr "" +#: shared-module/hmac/HMAC.c +msgid "key does not support this digest" +msgstr "" + +#: shared-module/hmac/HMAC.c +msgid "HMAC of an empty message is not supported with a hardware key" +msgstr "" + #: shared-module/i2cdisplaybus/I2CDisplayBus.c #: shared-module/is31fl3741/IS31FL3741.c #, c-format @@ -4521,6 +4676,28 @@ msgid "" "requires contiguous pins." msgstr "" +#: shared-module/rgbmatrix/RGBMatrix.c +msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] is not on the same port as clock" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] duplicates another pin assignment" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "" +"Pinout uses %d bytes per element, which consumes more than the ideal %d " +"bytes. If this cannot be avoided, pass allow_inefficient=True to the " +"constructor" +msgstr "" + #: shared-module/rgbmatrix/RGBMatrix.c msgid "No timer available" msgstr "사용 가능한 타이머가 없습니다" @@ -4769,6 +4946,10 @@ msgstr "" msgid "No IP" msgstr "IP가 없습니다" +#, fuzzy +#~ msgid "%q out of bounds" +#~ msgstr "%q가 경계를 벗어남" + #~ msgid "%q renamed %q" #~ msgstr "%q가 %q로 이름이 변경되었습니다" diff --git a/locale/ru.po b/locale/ru.po index 599f8b27e3e..79d4ce39234 100644 --- a/locale/ru.po +++ b/locale/ru.po @@ -102,6 +102,10 @@ msgstr "массив слишком велик" msgid "ndarray length overflows" msgstr "Переполнение длины массива ndarray" +#: extmod/ulab/code/ndarray.c +msgid "maximum number of dimensions is " +msgstr "Максимальное количество измерений составляет " + #: extmod/ulab/code/ndarray.c msgid "cannot convert complex type" msgstr "Не удается преобразовать сложный тип" @@ -155,14 +159,26 @@ msgstr "Тобайты могут быть вызваны только для п msgid "operation is not supported for given type" msgstr "Операция не поддерживается для данного типа" +#: extmod/ulab/code/ndarray.c +msgid "keyword argument must be tuple of integers" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "too many axes specified" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "axis index out of bounds" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "repeated indices" +msgstr "" + #: extmod/ulab/code/ndarray.c msgid "shape must be integer or tuple of integers" msgstr "фигура должна быть целым числом или кортежом целых чисел" -#: extmod/ulab/code/ndarray.c extmod/ulab/code/numpy/random/random.c -msgid "maximum number of dimensions is " -msgstr "Максимальное количество измерений составляет " - #: extmod/ulab/code/ndarray.c msgid "can only specify one unknown dimension" msgstr "Можно указать только одно неизвестное измерение" @@ -227,6 +243,31 @@ msgstr "Ввод должен быть 1D массивом ndarray" msgid "not implemented for complex dtype" msgstr "не реализовано для сложного типа d" +#: extmod/ulab/code/numpy/compare.c extmod/ulab/code/user/user.c +#: shared-bindings/_eve/__init__.c +msgid "input must be an ndarray" +msgstr "Ввод должен быть массивом ndarray" + +#: extmod/ulab/code/numpy/compare.c +msgid "object too deep for desired array" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast array data from dtype" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "minlength must not be negative" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "the weights and list don't have the same length" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast weigths to float" +msgstr "" + #: extmod/ulab/code/numpy/compare.c extmod/ulab/code/numpy/create.c #: extmod/ulab/code/numpy/io/io.c extmod/ulab/code/numpy/transform.c #: extmod/ulab/code/numpy/vector.c @@ -261,7 +302,7 @@ msgstr "только массивы ndarrays могут быть объедин msgid "wrong axis specified" msgstr "Указана неправильная ось" -#: extmod/ulab/code/numpy/create.c +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/utils/utils.c msgid "input arrays are not compatible" msgstr "Входные массивы несовместимы" @@ -273,6 +314,45 @@ msgstr "Вход должен быть 1- или 2-D" msgid "number of points must be at least 2" msgstr "Количество баллов должно быть не менее 2" +#: extmod/ulab/code/numpy/create.c +msgid "indexing should be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "indexing must be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "too many input arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: extmod/ulab/code/numpy/transform.c +msgid "arguments must be ndarrays" +msgstr "Аргументы должны быть массивами ndarrays" + +#: extmod/ulab/code/numpy/create.c +msgid "arguments must be 1D arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "input is not an array" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: ports/espressif/common-hal/pulseio/PulseIn.c +#: shared-bindings/bitmaptools/__init__.c +msgid "index out of range" +msgstr "индекс вне диапазона" + +#: extmod/ulab/code/numpy/create.c +msgid "mode should be raise, wrap or clip" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "index must not be negative" +msgstr "" + #: extmod/ulab/code/numpy/create.c msgid "offset must be non-negative and no greater than buffer length" msgstr "Смещение должно быть неотрицательным и не превышать длину буфера" @@ -293,7 +373,7 @@ msgstr "FFT определено только для массивов ndarrays" msgid "FFT is implemented for linear arrays only" msgstr "FFT реализовано только для линейных массивов" -#: extmod/ulab/code/numpy/fft/fft_tools.c +#: extmod/ulab/code/numpy/fft/fft_tools.c extmod/ulab/code/utils/utils.c msgid "input array length must be power of 2" msgstr "Длина входного массива должна быть равна степени 2" @@ -406,10 +486,6 @@ msgstr "сортировка arg не реализована для сглаже msgid "axis too long" msgstr "Слишком длинная ось" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/numpy/transform.c -msgid "arguments must be ndarrays" -msgstr "Аргументы должны быть массивами ndarrays" - #: extmod/ulab/code/numpy/numerical.c msgid "cross is defined for 1D arrays of length 3" msgstr "крест определяется для 1D массивов длины 3" @@ -418,12 +494,6 @@ msgstr "крест определяется для 1D массивов длин msgid "diff argument must be an ndarray" msgstr "аргумент DIFF должен быть массивом ndarray" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/ulab_tools.c -#: ports/espressif/common-hal/pulseio/PulseIn.c -#: shared-bindings/bitmaptools/__init__.c -msgid "index out of range" -msgstr "индекс вне диапазона" - #: extmod/ulab/code/numpy/numerical.c msgid "differentiation order out of range" msgstr "Порядок дифференциации вне диапазона" @@ -469,10 +539,10 @@ msgid "argument must be None, an integer or a tuple of integers" msgstr "аргумент должен быть None, целым числом или кортежем целых чисел" #: extmod/ulab/code/numpy/random/random.c -msgid "shape must be None, and integer or a tuple of integers" -msgstr "форма должна быть None, целым числом или кортежем целых чисел" +msgid "shape must be None, an integer or a tuple of integers" +msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "out has wrong type" msgstr "out имеет неправильный тип" @@ -484,7 +554,7 @@ msgstr "Выходной массив имеет неправильный тип msgid "size must match out.shape when used together" msgstr "Размер должен соответствовать out.shape при совместном использовании" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "output array must be contiguous" msgstr "выходной массив должен быть непрерывным" @@ -508,7 +578,7 @@ msgstr "неправильная длина массива индексов" msgid "dimensions do not match" msgstr "Размеры не совпадают" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/utils/utils.c msgid "out must be an ndarray" msgstr "out должен быть массивом ndarray" @@ -544,7 +614,7 @@ msgstr "Не может преобразовать сложный в плава msgid "input dtype must be float or complex" msgstr "Входной тип dtype должен быть плавающим или сложным" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/scipy/integrate/integrate.c msgid "first argument must be a callable" msgstr "Первый аргумент должен быть вызываемым" @@ -552,6 +622,28 @@ msgstr "Первый аргумент должен быть вызываемым msgid "wrong output type" msgstr "неверный тип вывода" +#: extmod/ulab/code/scipy/integrate/integrate.c +#, c-format +msgid "can't convert arg %d from %s to float" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "levels needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "steps needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +#: extmod/ulab/code/scipy/optimize/optimize.c +msgid "first argument must be a function" +msgstr "первый аргумент должен быть функцией" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "order needs to be a positive integer" +msgstr "" + #: extmod/ulab/code/scipy/linalg/linalg.c msgid "first two arguments must be ndarrays" msgstr "Первые два аргумента должны быть массивами ndarrays" @@ -560,10 +652,6 @@ msgstr "Первые два аргумента должны быть масси msgid "input must be a dense ndarray" msgstr "Ввод должен быть плотным массивом ndarray" -#: extmod/ulab/code/scipy/optimize/optimize.c -msgid "first argument must be a function" -msgstr "первый аргумент должен быть функцией" - #: extmod/ulab/code/scipy/optimize/optimize.c msgid "function has the same sign at the ends of interval" msgstr "функция имеет один и тот же знак в конце интервала" @@ -628,9 +716,17 @@ msgstr "размер определен только для массива ndarr msgid "input must be square matrix" msgstr "Входные данные должны быть квадратной матрицей" -#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c -msgid "input must be an ndarray" -msgstr "Ввод должен быть массивом ndarray" +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dtype" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dimension" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong shape" +msgstr "" #: extmod/ulab/code/utils/utils.c msgid "out must be a float dense array" @@ -644,6 +740,34 @@ msgstr "Смещение слишком большое" msgid "out array is too small" msgstr "Наш массив слишком мал" +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is defined for ndarrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is implemented for 1D arrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "out array must be a 1D array of float type" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "input and out arrays must have same length" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be an ndarray" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be a 1D dense float array" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be twice as long as input" +msgstr "" + #: extmod/vfs_fat.c py/moderrno.c msgid "Read-only filesystem" msgstr "Файловая система только для чтения" @@ -762,6 +886,7 @@ msgstr "" #: ports/analog/common-hal/busio/UART.c #: ports/atmel-samd/common-hal/busio/UART.c ports/cxd56/common-hal/busio/UART.c #: ports/nordic/common-hal/busio/UART.c ports/stm/common-hal/busio/UART.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "RS485" msgstr "RS485" @@ -774,7 +899,9 @@ msgstr "" msgid "Both RX and TX required for flow control" msgstr "Для управления потоком требуется как RX так и TX" -#: ports/analog/common-hal/busio/UART.c shared-module/rgbmatrix/RGBMatrix.c +#: ports/analog/common-hal/busio/UART.c +#: ports/raspberrypi/common-hal/usb_host/Port.c +#: shared-module/rgbmatrix/RGBMatrix.c msgid "Failed to allocate %q buffer" msgstr "Не удалось выделить буфер %q" @@ -787,7 +914,7 @@ msgid "UART transaction timeout" msgstr "" #: ports/analog/common-hal/busio/UART.c ports/espressif/common-hal/busio/UART.c -#: ports/nordic/common-hal/busio/UART.c +#: ports/nordic/common-hal/busio/UART.c ports/zephyr-cp/common-hal/busio/UART.c msgid "All UART peripherals are in use" msgstr "Все периферийные устройства UART уже используются" @@ -861,6 +988,9 @@ msgstr "Все таймеры уже используются" #: ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c +#: ports/zephyr-cp/common-hal/busio/I2C.c +#: ports/zephyr-cp/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "Internal resource(s) in use" msgstr "Используемые внутренние ресурсы" @@ -1028,6 +1158,21 @@ msgstr "Пин data 0 должен быть байтово выровнен" msgid "Bus pin %d is already in use" msgstr "Вывод шины %d уже используется" +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/espressif/common-hal/espidf/__init__.c +#: ports/raspberrypi/common-hal/picogame/Display.c +#: shared-bindings/picogame/Canvas.c shared-bindings/picogame/__init__.c +msgid "Operation or feature not supported" +msgstr "Операция или функция, не поддерживаемые" + +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c +#: shared-bindings/bitmapfilter/__init__.c +#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c +#: shared-module/synthio/__init__.c +msgid "%q must be of type %q, not %q" +msgstr "%q должно быть типа %q, а не %q" + #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nordic/common-hal/pulseio/PulseIn.c @@ -1195,11 +1340,6 @@ msgstr "Данные не поддерживаются направленным msgid "Timeout is too long: Maximum timeout length is %d seconds" msgstr "Таймаут слишком длинный: максимальная длина таймаута %d секунд" -#: ports/espressif/common-hal/_bleio/Characteristic.c -#: ports/espressif/common-hal/_bleio/Descriptor.c -msgid "MITM security not supported" -msgstr "Защита от MITM не поддерживается" - #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Descriptor.c @@ -1214,6 +1354,7 @@ msgstr "Длина значения > максимальная_длина" #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Service.c +#: ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c msgid "Too many %q" msgstr "" @@ -1228,6 +1369,20 @@ msgstr "Для этой характеристики нет CCCD" msgid "Can't set CCCD on local Characteristic" msgstr "Невозможно установить CCCD для локальной характеристики" +#: ports/espressif/common-hal/_bleio/Connection.c +#: ports/espressif/common-hal/_bleio/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c +#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c +#: shared-bindings/_bleio/CharacteristicBuffer.c +msgid "Not connected" +msgstr "Не подключено" + +#: ports/espressif/common-hal/_bleio/Connection.c +msgid "No pairing in progress" +msgstr "" + #: ports/espressif/common-hal/_bleio/Connection.c #: ports/nordic/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" @@ -1261,15 +1416,6 @@ msgstr "Изображение памяти" msgid "Invalid BLE parameter" msgstr "Недопустимый параметр BLE" -#: ports/espressif/common-hal/_bleio/__init__.c -#: ports/nordic/common-hal/_bleio/__init__.c -#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c -#: ports/zephyr-cp/common-hal/_bleio/Connection.c -#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c -#: shared-bindings/_bleio/CharacteristicBuffer.c -msgid "Not connected" -msgstr "Не подключено" - #: ports/espressif/common-hal/_bleio/__init__.c #: shared-module/audiofilewriter/AudioFileWriter.c msgid "Already in progress" @@ -1398,7 +1544,7 @@ msgstr "" #: ports/espressif/common-hal/busio/I2C.c #: ports/espressif/common-hal/i2ctarget/I2CTarget.c -#: ports/nordic/common-hal/busio/I2C.c +#: ports/nordic/common-hal/busio/I2C.c ports/zephyr-cp/common-hal/busio/I2C.c msgid "All I2C peripherals are in use" msgstr "Все периферийные устройства I2C уже используются" @@ -1412,6 +1558,7 @@ msgid "SPI configuration failed" msgstr "Сбой конфигурации SPI" #: ports/espressif/common-hal/busio/SPI.c ports/nordic/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/SPI.c msgid "All SPI peripherals are in use" msgstr "Все периферийные устройства SPI уже используются" @@ -1499,12 +1646,6 @@ msgstr "Неверный размер" msgid "Requested resource not found" msgstr "Запрошенный ресурс не найден" -#: ports/espressif/common-hal/espidf/__init__.c -#: ports/raspberrypi/common-hal/picogame/Display.c -#: shared-bindings/picogame/__init__.c -msgid "Operation or feature not supported" -msgstr "Операция или функция, не поддерживаемые" - #: ports/espressif/common-hal/espidf/__init__.c #: ports/espressif/common-hal/qspibus/QSPIBus.c msgid "Operation timed out" @@ -2006,13 +2147,6 @@ msgstr "Невозможно записать в постоянную памят msgid "All timers for this pin are in use" msgstr "Все таймеры для этого пина уже используются" -#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c -#: shared-bindings/bitmapfilter/__init__.c -#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c -#: shared-module/synthio/__init__.c -msgid "%q must be of type %q, not %q" -msgstr "%q должно быть типа %q, а не %q" - #: ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c msgid "Pins must be sequential GPIO pins" msgstr "Пины должны быть последовательными выводами GPIO" @@ -2441,6 +2575,10 @@ msgstr "функция отсутствует аргумент только по msgid "bad typecode" msgstr "Неверный шрифт" +#: py/binary.c +msgid "integer out of range" +msgstr "" + #: py/builtinevex.c msgid "bad compile mode" msgstr "Неверный режим компиляции" @@ -2566,6 +2704,10 @@ msgstr "super() не может найти себя" msgid "* arg after **" msgstr "* аргумент после **" +#: py/compile.c +msgid "* arg after kwarg" +msgstr "" + #: py/compile.c msgid "too many args" msgstr "слишком много аргументов" @@ -2694,6 +2836,22 @@ msgstr "" msgid "opcode '%q' argument %d: must not be zero" msgstr "" +#: py/emitinlinerv32.c +msgid "opcode '%q': registers must be different" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': malformed register list" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': invalid stack adjustment" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q' argument %d: wrong register(s)" +msgstr "" + #: py/emitinlinerv32.c msgid "invalid RV32 instruction '%q'" msgstr "" @@ -2855,6 +3013,10 @@ msgstr "кастинг" msgid "return expected '%q' but got '%q'" msgstr "Возврат ожидался '%q', но получил '%q'" +#: py/emitnative.c +msgid "native raise" +msgstr "" + #: py/emitnative.c msgid "must raise an object" msgstr "должен поднять объект" @@ -2867,9 +3029,9 @@ msgstr "родной урожай" msgid "unicode name escapes" msgstr "Экранирование имен в Юникоде" -#: py/modbuiltins.c -msgid "chr() arg not in range(0x110000)" -msgstr "chr() аргумент вне диапазона (0x110000)" +#: py/modbuiltins.c py/objstr.c +msgid "char not in range(0x110000)" +msgstr "" #: py/modbuiltins.c msgid "chr() arg not in range(256)" @@ -3176,11 +3338,6 @@ msgstr "Невозможно преобразовать %s в int" msgid "float too big" msgstr "Поплавок слишком большой" -#: py/objint.c -#, c-format -msgid "value must fit in %d byte(s)" -msgstr "Значение должно совпадать с байтами %d" - #: py/objint.c shared-bindings/time/__init__.c msgid "No long integer support" msgstr "Нет поддержки длинных целых чисел (long integer)" @@ -3194,6 +3351,15 @@ msgstr "Маленькое переполнение int" msgid "%q=%q" msgstr "%q=%q" +#: py/objint_impl.h +#, c-format +msgid "value would overflow a %d byte buffer" +msgstr "" + +#: py/objint_impl.h +msgid "can't convert negative int to unsigned" +msgstr "" + #: py/objint_longlong.c py/parsenum.c msgid "result overflows long long storage" msgstr "" @@ -3234,6 +3400,10 @@ msgstr "Шаг %q не может быть нулём" msgid "Cannot subclass slice" msgstr "Невозможно создать подкласс среза" +#: py/objstr.c +msgid "unknown encoding: %q" +msgstr "" + #: py/objstr.c msgid "bytes value out of range" msgstr "Значение байтов вне диапазона" @@ -3825,12 +3995,6 @@ msgstr "%q должен быть массивом байтов или масси msgid "Length of %q must be an even multiple of channel_count * type_size" msgstr "Длина %q должна быть четной, кратной количеству каналов * размер_типа" -#: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c -#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c -#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c -msgid "file must be a file opened in byte mode" -msgstr "Файл должен быть файлом, открытым в байтовом режиме" - #: shared-bindings/audiodelays/Chorus.c shared-bindings/audiodelays/Echo.c #: shared-bindings/audiodelays/Flanger.c #: shared-bindings/audiodelays/GranularPitchShift.c @@ -3859,6 +4023,12 @@ msgstr "" msgid "%q and %q must be different" msgstr "%q и %q должны быть разными" +#: shared-bindings/audiomp3/MP3Decoder.c +#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c +#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c +msgid "file must be a file opened in byte mode" +msgstr "Файл должен быть файлом, открытым в байтовом режиме" + #: shared-bindings/bitmapfilter/__init__.c msgid "" "weights must be a sequence with an odd square number of elements (usually 9 " @@ -4106,10 +4276,14 @@ msgstr "Буфер не является байтовым массивом." msgid "System entry must be gnss.SatelliteSystem" msgstr "Системная запись должна быть gnss. Спутниковая система" -#: shared-bindings/hashlib/__init__.c +#: shared-bindings/hashlib/__init__.c shared-bindings/hmac/__init__.c msgid "Unsupported hash algorithm" msgstr "Неподдерживаемый алгоритм хеширования" +#: shared-bindings/hmac/__init__.c +msgid "hardware key slot is unused" +msgstr "" + #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr "" @@ -4223,30 +4397,6 @@ msgstr "пустая последовательность" msgid "Publishers can only be created from a parent node" msgstr "" -#: shared-bindings/rgbmatrix/RGBMatrix.c -msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" -msgstr "Длина rgb_pins должна быть 6, 12, 18, 24 или 30" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] is not on the same port as clock" -msgstr "rgb_pins[%d] не находится на том же порту что и часы" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] duplicates another pin assignment" -msgstr "rgb_pins[%d] дублирует другое назначение пинов" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "" -"Pinout uses %d bytes per element, which consumes more than the ideal %d " -"bytes. If this cannot be avoided, pass allow_inefficient=True to the " -"constructor" -msgstr "" -"Распиновка использует %d байт на элемент, что превышает идеальное %d байт. " -"Если этого нельзя избежать, передайте конструктору allow_inefficient=True" - #: shared-bindings/rgbmatrix/RGBMatrix.c #, c-format msgid "Must use a multiple of 6 rgb pins, not %d" @@ -4330,10 +4480,6 @@ msgstr "" msgid "%q must be a subclass of %q" msgstr "%q должен быть подклассом %q" -#: shared-bindings/wifi/Monitor.c -msgid "%q out of bounds" -msgstr "%q за пределом" - #: shared-bindings/wifi/Radio.c msgid "Invalid hex password" msgstr "Неверный шестнадцатеричный пароль" @@ -4404,7 +4550,7 @@ msgstr "Не удалось выделить место для декодера" msgid "Failed to parse MP3 file" msgstr "Не удалось распарсить файл MP3" -#: shared-module/bitbangio/I2C.c +#: shared-module/bitbangio/I2C.c shared-module/msgpack/__init__.c msgid "%q too long" msgstr "%q слишком долго" @@ -4464,6 +4610,14 @@ msgstr "Неподдерживаемый тип шины дисплея" msgid "unsupported colorspace for GifWriter" msgstr "неподдерживаемое цветовое пространство для GifWriter" +#: shared-module/hmac/HMAC.c +msgid "key does not support this digest" +msgstr "" + +#: shared-module/hmac/HMAC.c +msgid "HMAC of an empty message is not supported with a hardware key" +msgstr "" + #: shared-module/i2cdisplaybus/I2CDisplayBus.c #: shared-module/is31fl3741/IS31FL3741.c #, c-format @@ -4539,6 +4693,30 @@ msgstr "" "Этот микроконтроллер поддерживает только data0=, а не data_pins=, поскольку " "для него требуются смежные выводы." +#: shared-module/rgbmatrix/RGBMatrix.c +msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" +msgstr "Длина rgb_pins должна быть 6, 12, 18, 24 или 30" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] is not on the same port as clock" +msgstr "rgb_pins[%d] не находится на том же порту что и часы" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] duplicates another pin assignment" +msgstr "rgb_pins[%d] дублирует другое назначение пинов" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "" +"Pinout uses %d bytes per element, which consumes more than the ideal %d " +"bytes. If this cannot be avoided, pass allow_inefficient=True to the " +"constructor" +msgstr "" +"Распиновка использует %d байт на элемент, что превышает идеальное %d байт. " +"Если этого нельзя избежать, передайте конструктору allow_inefficient=True" + #: shared-module/rgbmatrix/RGBMatrix.c msgid "No timer available" msgstr "Нет доступного таймера" @@ -4786,6 +4964,22 @@ msgstr "выключить" msgid "No IP" msgstr "Нет IP" +#~ msgid "shape must be None, and integer or a tuple of integers" +#~ msgstr "форма должна быть None, целым числом или кортежем целых чисел" + +#~ msgid "MITM security not supported" +#~ msgstr "Защита от MITM не поддерживается" + +#~ msgid "%q out of bounds" +#~ msgstr "%q за пределом" + +#~ msgid "chr() arg not in range(0x110000)" +#~ msgstr "chr() аргумент вне диапазона (0x110000)" + +#, c-format +#~ msgid "value must fit in %d byte(s)" +#~ msgstr "Значение должно совпадать с байтами %d" + #~ msgid "Too many descriptors" #~ msgstr "Слишком много дескрипторов" diff --git a/locale/tr.po b/locale/tr.po index df5bf887049..269ebf68c1e 100644 --- a/locale/tr.po +++ b/locale/tr.po @@ -101,6 +101,10 @@ msgstr "" msgid "ndarray length overflows" msgstr "" +#: extmod/ulab/code/ndarray.c +msgid "maximum number of dimensions is " +msgstr "" + #: extmod/ulab/code/ndarray.c msgid "cannot convert complex type" msgstr "" @@ -155,11 +159,23 @@ msgid "operation is not supported for given type" msgstr "" #: extmod/ulab/code/ndarray.c -msgid "shape must be integer or tuple of integers" +msgid "keyword argument must be tuple of integers" msgstr "" -#: extmod/ulab/code/ndarray.c extmod/ulab/code/numpy/random/random.c -msgid "maximum number of dimensions is " +#: extmod/ulab/code/ndarray.c +msgid "too many axes specified" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "axis index out of bounds" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "repeated indices" +msgstr "" + +#: extmod/ulab/code/ndarray.c +msgid "shape must be integer or tuple of integers" msgstr "" #: extmod/ulab/code/ndarray.c @@ -226,6 +242,31 @@ msgstr "" msgid "not implemented for complex dtype" msgstr "" +#: extmod/ulab/code/numpy/compare.c extmod/ulab/code/user/user.c +#: shared-bindings/_eve/__init__.c +msgid "input must be an ndarray" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "object too deep for desired array" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast array data from dtype" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "minlength must not be negative" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "the weights and list don't have the same length" +msgstr "" + +#: extmod/ulab/code/numpy/compare.c +msgid "cannot cast weigths to float" +msgstr "" + #: extmod/ulab/code/numpy/compare.c extmod/ulab/code/numpy/create.c #: extmod/ulab/code/numpy/io/io.c extmod/ulab/code/numpy/transform.c #: extmod/ulab/code/numpy/vector.c @@ -260,7 +301,7 @@ msgstr "" msgid "wrong axis specified" msgstr "" -#: extmod/ulab/code/numpy/create.c +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/utils/utils.c msgid "input arrays are not compatible" msgstr "" @@ -272,6 +313,45 @@ msgstr "" msgid "number of points must be at least 2" msgstr "" +#: extmod/ulab/code/numpy/create.c +msgid "indexing should be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "indexing must be 'xy' or 'ij'" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "too many input arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: extmod/ulab/code/numpy/transform.c +msgid "arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "arguments must be 1D arrays" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "input is not an array" +msgstr "" + +#: extmod/ulab/code/numpy/create.c extmod/ulab/code/numpy/numerical.c +#: ports/espressif/common-hal/pulseio/PulseIn.c +#: shared-bindings/bitmaptools/__init__.c +msgid "index out of range" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "mode should be raise, wrap or clip" +msgstr "" + +#: extmod/ulab/code/numpy/create.c +msgid "index must not be negative" +msgstr "" + #: extmod/ulab/code/numpy/create.c msgid "offset must be non-negative and no greater than buffer length" msgstr "" @@ -292,7 +372,7 @@ msgstr "FFT sadece ndarrays'te tanımlandı" msgid "FFT is implemented for linear arrays only" msgstr "FFT yalnızca doğrusal diziler için uygulanır" -#: extmod/ulab/code/numpy/fft/fft_tools.c +#: extmod/ulab/code/numpy/fft/fft_tools.c extmod/ulab/code/utils/utils.c msgid "input array length must be power of 2" msgstr "" @@ -401,10 +481,6 @@ msgstr "" msgid "axis too long" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/numpy/transform.c -msgid "arguments must be ndarrays" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "cross is defined for 1D arrays of length 3" msgstr "" @@ -413,12 +489,6 @@ msgstr "" msgid "diff argument must be an ndarray" msgstr "" -#: extmod/ulab/code/numpy/numerical.c extmod/ulab/code/ulab_tools.c -#: ports/espressif/common-hal/pulseio/PulseIn.c -#: shared-bindings/bitmaptools/__init__.c -msgid "index out of range" -msgstr "" - #: extmod/ulab/code/numpy/numerical.c msgid "differentiation order out of range" msgstr "" @@ -464,10 +534,10 @@ msgid "argument must be None, an integer or a tuple of integers" msgstr "" #: extmod/ulab/code/numpy/random/random.c -msgid "shape must be None, and integer or a tuple of integers" +msgid "shape must be None, an integer or a tuple of integers" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "out has wrong type" msgstr "" @@ -479,7 +549,7 @@ msgstr "" msgid "size must match out.shape when used together" msgstr "" -#: extmod/ulab/code/numpy/random/random.c +#: extmod/ulab/code/numpy/random/random.c extmod/ulab/code/ulab_tools.c msgid "output array must be contiguous" msgstr "" @@ -503,7 +573,7 @@ msgstr "" msgid "dimensions do not match" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/utils/utils.c msgid "out must be an ndarray" msgstr "" @@ -539,7 +609,7 @@ msgstr "" msgid "input dtype must be float or complex" msgstr "" -#: extmod/ulab/code/numpy/vector.c +#: extmod/ulab/code/numpy/vector.c extmod/ulab/code/scipy/integrate/integrate.c msgid "first argument must be a callable" msgstr "" @@ -547,18 +617,36 @@ msgstr "" msgid "wrong output type" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c -msgid "first two arguments must be ndarrays" +#: extmod/ulab/code/scipy/integrate/integrate.c +#, c-format +msgid "can't convert arg %d from %s to float" msgstr "" -#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c -msgid "input must be a dense ndarray" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "levels needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "steps needs to be a positive integer" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c #: extmod/ulab/code/scipy/optimize/optimize.c msgid "first argument must be a function" msgstr "" +#: extmod/ulab/code/scipy/integrate/integrate.c +msgid "order needs to be a positive integer" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c +msgid "first two arguments must be ndarrays" +msgstr "" + +#: extmod/ulab/code/scipy/linalg/linalg.c extmod/ulab/code/user/user.c +msgid "input must be a dense ndarray" +msgstr "" + #: extmod/ulab/code/scipy/optimize/optimize.c msgid "function has the same sign at the ends of interval" msgstr "" @@ -623,8 +711,16 @@ msgstr "" msgid "input must be square matrix" msgstr "" -#: extmod/ulab/code/user/user.c shared-bindings/_eve/__init__.c -msgid "input must be an ndarray" +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dtype" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong dimension" +msgstr "" + +#: extmod/ulab/code/ulab_tools.c +msgid "out array has wrong shape" msgstr "" #: extmod/ulab/code/utils/utils.c @@ -639,6 +735,34 @@ msgstr "" msgid "out array is too small" msgstr "" +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is defined for ndarrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "spectrogram is implemented for 1D arrays only" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "out array must be a 1D array of float type" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "input and out arrays must have same length" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be an ndarray" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be a 1D dense float array" +msgstr "" + +#: extmod/ulab/code/utils/utils.c +msgid "scratchpad must be twice as long as input" +msgstr "" + #: extmod/vfs_fat.c py/moderrno.c msgid "Read-only filesystem" msgstr "" @@ -758,6 +882,7 @@ msgstr "" #: ports/analog/common-hal/busio/UART.c #: ports/atmel-samd/common-hal/busio/UART.c ports/cxd56/common-hal/busio/UART.c #: ports/nordic/common-hal/busio/UART.c ports/stm/common-hal/busio/UART.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "RS485" msgstr "RS485" @@ -770,7 +895,9 @@ msgstr "" msgid "Both RX and TX required for flow control" msgstr "Hem RX hem de TX akış kontrolü için gerekli" -#: ports/analog/common-hal/busio/UART.c shared-module/rgbmatrix/RGBMatrix.c +#: ports/analog/common-hal/busio/UART.c +#: ports/raspberrypi/common-hal/usb_host/Port.c +#: shared-module/rgbmatrix/RGBMatrix.c msgid "Failed to allocate %q buffer" msgstr "" @@ -783,7 +910,7 @@ msgid "UART transaction timeout" msgstr "" #: ports/analog/common-hal/busio/UART.c ports/espressif/common-hal/busio/UART.c -#: ports/nordic/common-hal/busio/UART.c +#: ports/nordic/common-hal/busio/UART.c ports/zephyr-cp/common-hal/busio/UART.c msgid "All UART peripherals are in use" msgstr "Tüm UART çevre birimleri kullanımda" @@ -857,6 +984,9 @@ msgstr "Tüm zamanlayıcılar kullanımda" #: ports/atmel-samd/common-hal/rotaryio/IncrementalEncoder.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c +#: ports/zephyr-cp/common-hal/busio/I2C.c +#: ports/zephyr-cp/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/UART.c msgid "Internal resource(s) in use" msgstr "" @@ -1024,6 +1154,21 @@ msgstr "Data 0 pini bite hizalı olmalı" msgid "Bus pin %d is already in use" msgstr "Veriyolu pini %d kullanımda" +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/espressif/common-hal/espidf/__init__.c +#: ports/raspberrypi/common-hal/picogame/Display.c +#: shared-bindings/picogame/Canvas.c shared-bindings/picogame/__init__.c +msgid "Operation or feature not supported" +msgstr "" + +#: ports/atmel-samd/common-hal/picogame/Display.c +#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c +#: shared-bindings/bitmapfilter/__init__.c +#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c +#: shared-module/synthio/__init__.c +msgid "%q must be of type %q, not %q" +msgstr "%q; %q tipi olmalıdır, %q değil" + #: ports/atmel-samd/common-hal/pulseio/PulseIn.c #: ports/cxd56/common-hal/pulseio/PulseIn.c #: ports/nordic/common-hal/pulseio/PulseIn.c @@ -1192,11 +1337,6 @@ msgstr "Veri, hedefli reklamcılıkla desteklenmemektedir" msgid "Timeout is too long: Maximum timeout length is %d seconds" msgstr "" -#: ports/espressif/common-hal/_bleio/Characteristic.c -#: ports/espressif/common-hal/_bleio/Descriptor.c -msgid "MITM security not supported" -msgstr "" - #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Descriptor.c @@ -1211,6 +1351,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Service.c +#: ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c msgid "Too many %q" msgstr "" @@ -1225,6 +1366,20 @@ msgstr "" msgid "Can't set CCCD on local Characteristic" msgstr "Yerel Karakteristikte CCCD ayarlanamaz" +#: ports/espressif/common-hal/_bleio/Connection.c +#: ports/espressif/common-hal/_bleio/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c +#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c +#: shared-bindings/_bleio/CharacteristicBuffer.c +msgid "Not connected" +msgstr "" + +#: ports/espressif/common-hal/_bleio/Connection.c +msgid "No pairing in progress" +msgstr "" + #: ports/espressif/common-hal/_bleio/Connection.c #: ports/nordic/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" @@ -1258,15 +1413,6 @@ msgstr "" msgid "Invalid BLE parameter" msgstr "Geçersiz BLE parametresi" -#: ports/espressif/common-hal/_bleio/__init__.c -#: ports/nordic/common-hal/_bleio/__init__.c -#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c -#: ports/zephyr-cp/common-hal/_bleio/Connection.c -#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c -#: shared-bindings/_bleio/CharacteristicBuffer.c -msgid "Not connected" -msgstr "" - #: ports/espressif/common-hal/_bleio/__init__.c #: shared-module/audiofilewriter/AudioFileWriter.c msgid "Already in progress" @@ -1394,7 +1540,7 @@ msgstr "" #: ports/espressif/common-hal/busio/I2C.c #: ports/espressif/common-hal/i2ctarget/I2CTarget.c -#: ports/nordic/common-hal/busio/I2C.c +#: ports/nordic/common-hal/busio/I2C.c ports/zephyr-cp/common-hal/busio/I2C.c msgid "All I2C peripherals are in use" msgstr "Tüm I2C çevre birimleri kullanımda" @@ -1408,6 +1554,7 @@ msgid "SPI configuration failed" msgstr "" #: ports/espressif/common-hal/busio/SPI.c ports/nordic/common-hal/busio/SPI.c +#: ports/zephyr-cp/common-hal/busio/SPI.c msgid "All SPI peripherals are in use" msgstr "Tüm SPI çevre birimleri kullanımda" @@ -1493,12 +1640,6 @@ msgstr "Geçersiz boyut" msgid "Requested resource not found" msgstr "" -#: ports/espressif/common-hal/espidf/__init__.c -#: ports/raspberrypi/common-hal/picogame/Display.c -#: shared-bindings/picogame/__init__.c -msgid "Operation or feature not supported" -msgstr "" - #: ports/espressif/common-hal/espidf/__init__.c #: ports/espressif/common-hal/qspibus/QSPIBus.c msgid "Operation timed out" @@ -1998,13 +2139,6 @@ msgstr "" msgid "All timers for this pin are in use" msgstr "Bu pin için tüm zamanlayıcılar kullanımda" -#: ports/raspberrypi/common-hal/picogame/Display.c py/argcheck.c py/runtime.c -#: shared-bindings/bitmapfilter/__init__.c -#: shared-module/audiodelays/MultiTapDelay.c shared-module/synthio/Note.c -#: shared-module/synthio/__init__.c -msgid "%q must be of type %q, not %q" -msgstr "%q; %q tipi olmalıdır, %q değil" - #: ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c msgid "Pins must be sequential GPIO pins" msgstr "" @@ -2426,6 +2560,10 @@ msgstr "" msgid "bad typecode" msgstr "" +#: py/binary.c +msgid "integer out of range" +msgstr "" + #: py/builtinevex.c msgid "bad compile mode" msgstr "" @@ -2544,6 +2682,10 @@ msgstr "" msgid "* arg after **" msgstr "" +#: py/compile.c +msgid "* arg after kwarg" +msgstr "" + #: py/compile.c msgid "too many args" msgstr "" @@ -2672,6 +2814,22 @@ msgstr "" msgid "opcode '%q' argument %d: must not be zero" msgstr "" +#: py/emitinlinerv32.c +msgid "opcode '%q': registers must be different" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': malformed register list" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q': invalid stack adjustment" +msgstr "" + +#: py/emitinlinerv32.c +msgid "opcode '%q' argument %d: wrong register(s)" +msgstr "" + #: py/emitinlinerv32.c msgid "invalid RV32 instruction '%q'" msgstr "" @@ -2833,6 +2991,10 @@ msgstr "" msgid "return expected '%q' but got '%q'" msgstr "" +#: py/emitnative.c +msgid "native raise" +msgstr "" + #: py/emitnative.c msgid "must raise an object" msgstr "" @@ -2845,8 +3007,8 @@ msgstr "" msgid "unicode name escapes" msgstr "" -#: py/modbuiltins.c -msgid "chr() arg not in range(0x110000)" +#: py/modbuiltins.c py/objstr.c +msgid "char not in range(0x110000)" msgstr "" #: py/modbuiltins.c @@ -3149,11 +3311,6 @@ msgstr "" msgid "float too big" msgstr "" -#: py/objint.c -#, c-format -msgid "value must fit in %d byte(s)" -msgstr "" - #: py/objint.c shared-bindings/time/__init__.c msgid "No long integer support" msgstr "" @@ -3167,6 +3324,15 @@ msgstr "" msgid "%q=%q" msgstr "%q=%q" +#: py/objint_impl.h +#, c-format +msgid "value would overflow a %d byte buffer" +msgstr "" + +#: py/objint_impl.h +msgid "can't convert negative int to unsigned" +msgstr "" + #: py/objint_longlong.c py/parsenum.c msgid "result overflows long long storage" msgstr "" @@ -3207,6 +3373,10 @@ msgstr "%q sıfır olamaz" msgid "Cannot subclass slice" msgstr "Alt sınıf kesilemez" +#: py/objstr.c +msgid "unknown encoding: %q" +msgstr "" + #: py/objstr.c msgid "bytes value out of range" msgstr "" @@ -3786,12 +3956,6 @@ msgstr "%q 'h', 'H', 'b' ya da 'B' tipi bir bytearray ya da array olmalı" msgid "Length of %q must be an even multiple of channel_count * type_size" msgstr "" -#: shared-bindings/audiocore/WaveFile.c shared-bindings/audiomp3/MP3Decoder.c -#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c -#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c -msgid "file must be a file opened in byte mode" -msgstr "" - #: shared-bindings/audiodelays/Chorus.c shared-bindings/audiodelays/Echo.c #: shared-bindings/audiodelays/Flanger.c #: shared-bindings/audiodelays/GranularPitchShift.c @@ -3820,6 +3984,12 @@ msgstr "" msgid "%q and %q must be different" msgstr "%q ve %q farklı olmalılar" +#: shared-bindings/audiomp3/MP3Decoder.c +#: shared-bindings/displayio/OnDiskBitmap.c shared-bindings/gifio/OnDiskGif.c +#: shared-bindings/synthio/__init__.c shared-module/gifio/GifWriter.c +msgid "file must be a file opened in byte mode" +msgstr "" + #: shared-bindings/bitmapfilter/__init__.c msgid "" "weights must be a sequence with an odd square number of elements (usually 9 " @@ -4057,10 +4227,14 @@ msgstr "Buffer bir bytearray değil." msgid "System entry must be gnss.SatelliteSystem" msgstr "" -#: shared-bindings/hashlib/__init__.c +#: shared-bindings/hashlib/__init__.c shared-bindings/hmac/__init__.c msgid "Unsupported hash algorithm" msgstr "" +#: shared-bindings/hmac/__init__.c +msgid "hardware key slot is unused" +msgstr "" + #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr "" @@ -4172,28 +4346,6 @@ msgstr "" msgid "Publishers can only be created from a parent node" msgstr "" -#: shared-bindings/rgbmatrix/RGBMatrix.c -msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] is not on the same port as clock" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "rgb_pins[%d] duplicates another pin assignment" -msgstr "" - -#: shared-bindings/rgbmatrix/RGBMatrix.c -#, c-format -msgid "" -"Pinout uses %d bytes per element, which consumes more than the ideal %d " -"bytes. If this cannot be avoided, pass allow_inefficient=True to the " -"constructor" -msgstr "" - #: shared-bindings/rgbmatrix/RGBMatrix.c #, c-format msgid "Must use a multiple of 6 rgb pins, not %d" @@ -4274,10 +4426,6 @@ msgstr "" msgid "%q must be a subclass of %q" msgstr "%q, %q'nün alt türü olmalıdır" -#: shared-bindings/wifi/Monitor.c -msgid "%q out of bounds" -msgstr "%q sınırların dışında" - #: shared-bindings/wifi/Radio.c msgid "Invalid hex password" msgstr "" @@ -4348,7 +4496,7 @@ msgstr "Deşifre edici tahsis edilemedi" msgid "Failed to parse MP3 file" msgstr "MP3 dosyası ayrıştırılamadı" -#: shared-module/bitbangio/I2C.c +#: shared-module/bitbangio/I2C.c shared-module/msgpack/__init__.c msgid "%q too long" msgstr "%q çok uzun" @@ -4407,6 +4555,14 @@ msgstr "" msgid "unsupported colorspace for GifWriter" msgstr "" +#: shared-module/hmac/HMAC.c +msgid "key does not support this digest" +msgstr "" + +#: shared-module/hmac/HMAC.c +msgid "HMAC of an empty message is not supported with a hardware key" +msgstr "" + #: shared-module/i2cdisplaybus/I2CDisplayBus.c #: shared-module/is31fl3741/IS31FL3741.c #, c-format @@ -4480,6 +4636,28 @@ msgid "" "requires contiguous pins." msgstr "" +#: shared-module/rgbmatrix/RGBMatrix.c +msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] is not on the same port as clock" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "rgb_pins[%d] duplicates another pin assignment" +msgstr "" + +#: shared-module/rgbmatrix/RGBMatrix.c +#, c-format +msgid "" +"Pinout uses %d bytes per element, which consumes more than the ideal %d " +"bytes. If this cannot be avoided, pass allow_inefficient=True to the " +"constructor" +msgstr "" + #: shared-module/rgbmatrix/RGBMatrix.c msgid "No timer available" msgstr "" @@ -4725,6 +4903,9 @@ msgstr "" msgid "No IP" msgstr "IP yok" +#~ msgid "%q out of bounds" +#~ msgstr "%q sınırların dışında" + #, c-format #~ msgid "Buffer + offset too small %d %d %d" #~ msgstr "Buffer + offset çok küçük %d %d %d" diff --git a/main.c b/main.c index c860e739275..c7f0ba76e37 100644 --- a/main.c +++ b/main.c @@ -15,6 +15,7 @@ #include "py/compile.h" #include "py/frozenmod.h" #include "py/mphal.h" +#include "py/persistentcode.h" #include "py/runtime.h" #include "py/repl.h" #include "py/gc.h" @@ -80,6 +81,10 @@ #include "shared-bindings/epaperdisplay/EPaperDisplay.h" #endif +#if CIRCUITPY_PICOGAME_RGB444 +#include "shared-module/picogame/__init__.h" +#endif + #if CIRCUITPY_KEYPAD #include "shared-module/keypad/__init__.h" #endif @@ -249,6 +254,7 @@ static void stop_mp(void) { qstr_reset(); gc_deinit(); + port_gc_deinit(); port_free(_heap); _heap = NULL; @@ -373,6 +379,11 @@ static void cleanup_after_vm(mp_obj_t exception) { atexit_reset(); #endif + // Restore the panel's pixel format while the display bus is still alive. + #if CIRCUITPY_PICOGAME_RGB444 + picogame_reset(); + #endif + // Turn off the display and flush the filesystem before the heap disappears. #if CIRCUITPY_DISPLAYIO reset_displays(); @@ -387,7 +398,9 @@ static void cleanup_after_vm(mp_obj_t exception) { bleio_user_reset(); #endif - #if CIRCUITPY_CANIO + #if CIRCUITPY_CANIO && CIRCUITPY_BULK_RESET + // Bulk-reset ports reset CAN controllers here. Finalizer ports rely on + // GC finalizers calling deinit on the CAN objects. common_hal_canio_reset(); #endif @@ -424,7 +437,9 @@ static void cleanup_after_vm(mp_obj_t exception) { stop_mp(); // Don't reset pins until finalisers have run. + #if CIRCUITPY_BULK_RESET reset_all_pins(); + #endif // Let the workflows know we've reset in case they want to restart. supervisor_workflow_reset(); @@ -804,7 +819,9 @@ static bool __attribute__((noinline)) run_code_py(safe_mode_t safe_mode, bool *s common_hal_alarm_clear_pin_preservations(); #endif // Reset pins, as if there was a hard reset. + #if CIRCUITPY_BULK_RESET reset_all_pins(); + #endif // Pretend that the next run is the first run, as if we were reset. *simulate_reset = true; } @@ -875,8 +892,8 @@ static void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) { #endif // Get the base filesystem. - fs_user_mount_t *vfs = filesystem_circuitpy(); - FATFS *fs = &vfs->fatfs; + supervisor_vfs_t *vfs_root = filesystem_circuitpy(); + fs_user_mount_t *vfs = vfs_root == NULL ? NULL : &vfs_root->fat; // Allow boot.py access to CIRCUITPY, and allow writes to boot_out.txt. // We can't use the regular flags for this, because they might get modified inside boot.py. @@ -884,6 +901,9 @@ static void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) { // Write version info mp_printf(&mp_plat_print, "%s\nBoard ID:%s\n", MICROPY_FULL_VERSION_INFO, CIRCUITPY_BOARD_ID); + #if MICROPY_PERSISTENT_CODE_LOAD_NATIVE + mp_printf(&mp_plat_print, "%q:%s\n", MP_QSTR_architecture, MPY_FEATURE_ARCH_NAME); + #endif #if CIRCUITPY_MICROCONTROLLER && COMMON_HAL_MCU_PROCESSOR_UID_LENGTH > 0 uint8_t raw_id[COMMON_HAL_MCU_PROCESSOR_UID_LENGTH]; common_hal_mcu_processor_get_uid(raw_id); @@ -905,15 +925,17 @@ static void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) { supervisor_status_bar_resume(); #endif bool write_boot_output = true; - FIL boot_output_file; - if (f_open(fs, &boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_READ) == FR_OK) { + supervisor_vfs_file_t boot_output_file; + if (supervisor_vfs_open_file(vfs_root, CIRCUITPY_BOOT_OUTPUT_FILE, SUPERVISOR_FS_OPEN_READ, 0, + &boot_output_file) == SUPERVISOR_FS_OK) { char *file_contents = m_new(char, boot_text.alloc); - UINT chars_read; - if (f_read(&boot_output_file, file_contents, 1 + boot_text.len, &chars_read) == FR_OK) { + size_t chars_read; + if (supervisor_vfs_read_file(&boot_output_file, file_contents, 1 + boot_text.len, &chars_read) == + SUPERVISOR_FS_OK) { write_boot_output = (chars_read != boot_text.len) || (memcmp(boot_text.buf, file_contents, chars_read) != 0); } - // no need to f_close the file + supervisor_vfs_close_file(&boot_output_file); } if (write_boot_output) { @@ -921,11 +943,14 @@ static void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) { // in case power is momentary or will fail shortly due to, say a low, battery. mp_hal_delay_ms(1000); - f_open(fs, &boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_WRITE | FA_CREATE_ALWAYS); - UINT chars_written; - f_write(&boot_output_file, boot_text.buf, boot_text.len, &chars_written); - f_close(&boot_output_file); - filesystem_flush(); + if (supervisor_vfs_open_file(vfs_root, CIRCUITPY_BOOT_OUTPUT_FILE, + SUPERVISOR_FS_OPEN_WRITE | SUPERVISOR_FS_OPEN_CREATE | SUPERVISOR_FS_OPEN_TRUNCATE, 0, + &boot_output_file) == SUPERVISOR_FS_OK) { + size_t chars_written; + supervisor_vfs_write_file(&boot_output_file, boot_text.buf, boot_text.len, &chars_written); + supervisor_vfs_close_file(&boot_output_file); + filesystem_flush(); + } } #endif @@ -1020,7 +1045,9 @@ int __attribute__((used)) main(void) { set_safe_mode(port_init()); // All ports need pins reset, after never-reset pins are marked in port_init(); + #if CIRCUITPY_BULK_RESET reset_all_pins(); + #endif port_heap_init(); diff --git a/mpy-cross/main.c b/mpy-cross/main.c index 8cdd124ef46..7d829e79e68 100644 --- a/mpy-cross/main.c +++ b/mpy-cross/main.c @@ -24,6 +24,7 @@ * THE SOFTWARE. */ +#include #include #include #include @@ -34,7 +35,7 @@ #include "py/persistentcode.h" #include "py/runtime.h" #include "py/gc.h" -#include "py/parsenum.h" +#include "py/parsenumbase.h" #include "genhdr/mpversion.h" #ifdef _WIN32 // CIRCUITPY-CHANGE @@ -49,7 +50,10 @@ static asm_rv32_backend_options_t rv32_options = { 0 }; // Command line options, with their defaults static uint emit_opt = MP_EMIT_OPT_NONE; -mp_uint_t mp_verbose_flag = 0; + +#if MICROPY_ENABLE_SOURCE_LINE +static bool include_source_lines = true; +#endif // Heap size of GC heap (if enabled) // Make it larger on a 64 bit machine, because pointers are larger. @@ -164,6 +168,12 @@ static int usage(char **argv) { " heapsize= -- set the heap size for the GC (default %ld)\n" , heap_size); impl_opts_cnt++; + #if MICROPY_ENABLE_SOURCE_LINE + printf( + " source-lines -- include source line numbers (default)\n" + " no-source-lines -- exclude source line numbers\n"); + impl_opts_cnt += 2; + #endif if (impl_opts_cnt == 0) { printf(" (none)\n"); @@ -188,6 +198,14 @@ static void pre_process_options(int argc, char **argv) { } else if (strcmp(argv[a + 1], "emit=viper") == 0) { emit_opt = MP_EMIT_OPT_VIPER; #endif + #if MICROPY_ENABLE_SOURCE_LINE + } else if (strcmp(argv[a + 1], "source-lines") == 0) { + // Allow excluding source lines for debug builds. + include_source_lines = true; + } else if (strcmp(argv[a + 1], "no-source-lines") == 0) { + // Allow excluding source lines for debug builds. + include_source_lines = false; + #endif } else if (strncmp(argv[a + 1], "heapsize=", sizeof("heapsize=") - 1) == 0) { char *end; heap_size = strtol(argv[a + 1] + sizeof("heapsize=") - 1, &end, 0); @@ -229,45 +247,35 @@ static char *backslash_to_forwardslash(char *path) { } // This will need to be reworked in case mpy-cross needs to set more bits than -// what its small int representation allows to fit in there. -static bool parse_integer(const char *value, mp_uint_t *integer) { +// what `unsigned long` can fit. +static bool parse_integer(const char *value, unsigned long *integer) { assert(value && "Attempting to parse a NULL string"); assert(integer && "Attempting to store into a NULL integer buffer"); size_t value_length = strlen(value); - int base = 10; - if (value_length > 2 && value[0] == '0') { - if ((value[1] | 0x20) == 'b') { - base = 2; - } else if ((value[1] | 0x20) == 'x') { - base = 16; - } else { - return false; - } + int base = 0; + size_t skip = mp_parse_num_base(value, value_length, &base); + // These can trip strtoul up. + if (base < 2 || value_length == skip || value[skip] == '+') { + return false; } - - bool valid = false; - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - mp_obj_t parsed = mp_parse_num_integer(value, value_length, base, NULL); - if (mp_obj_is_small_int(parsed)) { - *integer = MP_OBJ_SMALL_INT_VALUE(parsed); - valid = true; - } - nlr_pop(); + errno = 0; + char *end = NULL; + *integer = strtoul(value + skip, &end, base); + if (end != (value + value_length) || errno != 0) { + return false; } - - return valid; + return true; } #if MICROPY_EMIT_NATIVE && MICROPY_EMIT_RV32 -static bool parse_rv32_flags_string(const char *source, mp_uint_t *flags) { +static bool parse_rv32_flags_string(const char *source, unsigned long *flags) { assert(source && "Flag arguments string is NULL."); assert(flags && "Collected flags pointer is NULL."); const char *current = source; const char *end = source + strlen(source); - mp_uint_t collected_flags = 0; + unsigned long collected_flags = 0; while (current < end) { const char *separator = strchr(current, ','); if (separator == NULL) { @@ -312,6 +320,9 @@ MP_NOINLINE int main_(int argc, char **argv) { mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_NONE; mp_dynamic_compiler.nlr_buf_num_regs = 0; mp_dynamic_compiler.backend_options = NULL; + #if MICROPY_ENABLE_SOURCE_LINE + mp_dynamic_compiler.include_source_lines = include_source_lines; + #endif const char *input_file = NULL; const char *output_file = NULL; @@ -330,7 +341,7 @@ MP_NOINLINE int main_(int argc, char **argv) { "; mpy-cross emitting mpy v" MP_STRINGIFY(MPY_VERSION) "." MP_STRINGIFY(MPY_SUB_VERSION) "\n"); return 0; } else if (strcmp(argv[a], "-v") == 0) { - mp_verbose_flag++; + // This verbose option doesn't currently do anything. } else if (strncmp(argv[a], "-O", 2) == 0) { if (unichar_isdigit(argv[a][2])) { MP_STATE_VM(mp_optimise_value) = argv[a][2] & 0xf; @@ -441,10 +452,10 @@ MP_NOINLINE int main_(int argc, char **argv) { #if MICROPY_EMIT_NATIVE && MICROPY_EMIT_RV32 if (mp_dynamic_compiler.native_arch == MP_NATIVE_ARCH_RV32IMC) { mp_dynamic_compiler.backend_options = (void *)&rv32_options; - mp_uint_t raw_flags = 0; + unsigned long raw_flags = 0; if (parse_integer(arch_flags, &raw_flags) || parse_rv32_flags_string(arch_flags, &raw_flags)) { - if ((raw_flags & ~((mp_uint_t)RV32_EXT_ALL)) == 0) { - rv32_options.allowed_extensions = raw_flags; + if ((raw_flags & ~((unsigned long)RV32_EXT_ALL)) == 0) { + rv32_options.allowed_extensions = (uint8_t)raw_flags; processed = true; } } @@ -472,12 +483,6 @@ MP_NOINLINE int main_(int argc, char **argv) { int ret = compile_and_save(input_file, output_file, source_file); - #if MICROPY_PY_MICROPYTHON_MEM_INFO - if (mp_verbose_flag) { - mp_micropython_mem_info(0, NULL); - } - #endif - mp_deinit(); return ret & 0xff; diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 56c5963fa9c..3f8e54d878b 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -88,9 +88,13 @@ #define MICROPY_PY_TSTRINGS (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) -#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) -// Fall back to setjmp() implementation for discovery of GC pointers in registers. -#define MICROPY_GCREGS_SETJMP (1) +// Fall back to setjmp() implementation for discovery of GC pointers in registers +// if running on intel-based macOS, or on architectures for which there is no +// specialised GC pointer discovery mechanism. +#if (defined(__APPLE__) && defined(__MACH__) && (defined(__i386__) || defined(__x86_64__))) || \ + (!(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || \ + defined(__thumb2__) || defined(__thumb__) || defined(__arm__))) +#define MICROPY_GCREGS_SETJMP (1) #endif #define MICROPY_MODULE___FILE__ (0) diff --git a/ports/analog/common-hal/busio/I2C.c b/ports/analog/common-hal/busio/I2C.c index 7ebe721b3f4..2e9a43dacb6 100644 --- a/ports/analog/common-hal/busio/I2C.c +++ b/ports/analog/common-hal/busio/I2C.c @@ -96,12 +96,6 @@ void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, return; } -// Never reset I2C obj when reload -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - common_hal_never_reset_pin(self->sda); - common_hal_never_reset_pin(self->scl); -} - // Check I2C status, deinited or not bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { return self->sda == NULL; diff --git a/ports/analog/common-hal/busio/SPI.c b/ports/analog/common-hal/busio/SPI.c index bdbe6da9d94..e74df040f48 100644 --- a/ports/analog/common-hal/busio/SPI.c +++ b/ports/analog/common-hal/busio/SPI.c @@ -114,14 +114,6 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, return; } -// Never reset SPI when reload -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { - common_hal_never_reset_pin(self->mosi); - common_hal_never_reset_pin(self->miso); - common_hal_never_reset_pin(self->sck); - common_hal_never_reset_pin(self->nss); -} - // Check SPI status, deinited or not bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { return self->sck == NULL; diff --git a/ports/analog/common-hal/busio/UART.c b/ports/analog/common-hal/busio/UART.c index be7851f52a2..1390a0b3345 100644 --- a/ports/analog/common-hal/busio/UART.c +++ b/ports/analog/common-hal/busio/UART.c @@ -64,7 +64,6 @@ typedef enum { typedef enum { UART_FREE = 0, UART_BUSY, - UART_NEVER_RESET, } uart_status_t; static uint32_t timeout_ms = 0; @@ -74,7 +73,6 @@ static uint32_t timeout_ms = 0; static uint8_t uarts_active = 0; static uart_status_t uart_status[NUM_UARTS]; static volatile int uart_err; -static uint8_t uart_never_reset_mask = 0; static busio_uart_obj_t *context; static bool isValidBaudrate(uint32_t baudrate) { @@ -449,12 +447,4 @@ bool common_hal_busio_uart_ready_to_tx(busio_uart_obj_t *self) { return !(MXC_UART_GetStatus(self->uart_regs) & (MXC_F_UART_STATUS_TX_BUSY)); } -void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { - common_hal_never_reset_pin(self->tx_pin); - common_hal_never_reset_pin(self->rx_pin); - common_hal_never_reset_pin(self->cts_pin); - common_hal_never_reset_pin(self->rts_pin); - uart_never_reset_mask |= (1 << (self->uart_id)); -} - #endif // CIRCUITPY_BUSIO_UART diff --git a/ports/analog/common-hal/digitalio/DigitalInOut.c b/ports/analog/common-hal/digitalio/DigitalInOut.c index 93e2242fbb6..e00347b38ce 100644 --- a/ports/analog/common-hal/digitalio/DigitalInOut.c +++ b/ports/analog/common-hal/digitalio/DigitalInOut.c @@ -14,11 +14,6 @@ extern mxc_gpio_regs_t *gpio_ports[NUM_GPIO_PORTS]; -void common_hal_digitalio_digitalinout_never_reset( - digitalio_digitalinout_obj_t *self) { - common_hal_never_reset_pin(self->pin); -} - bool common_hal_digitalio_digitalinout_deinited(digitalio_digitalinout_obj_t *self) { return self->pin == NULL; } diff --git a/ports/analog/common-hal/microcontroller/Pin.c b/ports/analog/common-hal/microcontroller/Pin.c index 83e2f3b9c3a..1e35ebe0a6f 100644 --- a/ports/analog/common-hal/microcontroller/Pin.c +++ b/ports/analog/common-hal/microcontroller/Pin.c @@ -19,22 +19,8 @@ static uint32_t claimed_pins[NUM_GPIO_PORTS]; // defined in board.c extern mxc_gpio_regs_t *gpio_ports[NUM_GPIO_PORTS]; -static uint32_t never_reset_pins[NUM_GPIO_PORTS]; - #define INVALID_PIN 0xFF // id for invalid pin -void reset_all_pins(void) { - // reset all pins except for never_reset_pins - for (int i = 0; i < NUM_GPIO_PORTS; i++) { - for (int j = 0; j < 32; j++) { - if (!(never_reset_pins[i] & (1 << j))) { - reset_pin_number(i, j); - } - } - // set claimed pins to never_reset pins - claimed_pins[i] = never_reset_pins[i]; - } -} void reset_pin_number(uint8_t pin_port, uint8_t pin_pad) { if ((pin_port == INVALID_PIN) || (pin_port > NUM_GPIO_PORTS)) { @@ -84,15 +70,6 @@ bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { return !(claimed_pins[pin->port] & (pin->mask)); } -void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - if ((pin != NULL) && (pin->mask != INVALID_PIN)) { - never_reset_pins[pin->port] |= (1 << pin->mask); - - // any never reset pin must also be claimed - claimed_pins[pin->port] |= (1 << pin->mask); - } -} - void common_hal_reset_pin(const mcu_pin_obj_t *pin) { if (pin == NULL) { return; diff --git a/ports/analog/common-hal/microcontroller/Pin.h b/ports/analog/common-hal/microcontroller/Pin.h index 169586e7e90..18f3700cf5d 100644 --- a/ports/analog/common-hal/microcontroller/Pin.h +++ b/ports/analog/common-hal/microcontroller/Pin.h @@ -10,7 +10,10 @@ #include "peripherals/pins.h" -void reset_all_pins(void); +void common_hal_reset_pin(const mcu_pin_obj_t *pin); // reset_pin_number takes the pin number instead of the pointer so that objects don't // need to store a full pointer. void reset_pin_number(uint8_t pin_port, uint8_t pin_pad); + +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); +void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin); diff --git a/ports/analog/common-hal/rtc/RTC.h b/ports/analog/common-hal/rtc/RTC.h index 590bc93ab52..f68897c19fb 100644 --- a/ports/analog/common-hal/rtc/RTC.h +++ b/ports/analog/common-hal/rtc/RTC.h @@ -7,5 +7,3 @@ // SPDX-License-Identifier: MIT #pragma once - -extern void rtc_reset(void); diff --git a/ports/analog/linking/max32690_cktpy.ld b/ports/analog/linking/max32690_cktpy.ld index 9b32121a135..e802a8f9612 100644 --- a/ports/analog/linking/max32690_cktpy.ld +++ b/ports/analog/linking/max32690_cktpy.ld @@ -41,6 +41,12 @@ SECTIONS { .text : { . = ALIGN(4); + __property_getter_start = .; + *(.property_getter) + __property_getter_end = .; + __property_getset_start = .; + *(.property_getset) + __property_getset_end = .; _text = .; /* ISR Vector beginning of .text */ @@ -183,4 +189,9 @@ SECTIONS { /* Check if data + heap + stack exceeds RAM limit */ ASSERT(__StackLimit >= _ebss, "region RAM overflowed with stack") + + ASSERT((__property_getter_end - __property_getter_start) % 8 == 0, + "the .property_getter section must hold whole 8-byte objects") + ASSERT((__property_getset_end - __property_getset_start) % 12 == 0, + "the .property_getset section must hold whole 12-byte objects") } diff --git a/ports/analog/mpconfigport.mk b/ports/analog/mpconfigport.mk index f1cd0bb2901..86eca7f6cde 100644 --- a/ports/analog/mpconfigport.mk +++ b/ports/analog/mpconfigport.mk @@ -6,6 +6,9 @@ # # SPDX-License-Identifier: MIT +# Properties get a dedicated linker section here, so they can drop the unused slots. +CIRCUITPY_OPTIMIZE_PROPERTY_FLASH_SIZE ?= 1 + CHIP_FAMILY ?= max32 # Necessary to build CircuitPython @@ -72,3 +75,6 @@ CIRCUITPY_BUSDEVICE = 0 CIRCUITPY_BUILD_EXTENSIONS ?= elf CIRCUITPY_PORT_SERIAL = 1 + +# Cleanup is performed by GC finalizers (__del__ -> deinit) instead of a bulk pin reset. +CIRCUITPY_BULK_RESET = 0 diff --git a/ports/analog/supervisor/usb.c b/ports/analog/supervisor/usb.c index 1624359ab51..c971b4dab2c 100644 --- a/ports/analog/supervisor/usb.c +++ b/ports/analog/supervisor/usb.c @@ -19,7 +19,6 @@ void init_usb_hardware(void) { // USB GPIOs are non-configurable on MAX32 devices - // No need to add them to the never_reset list for mcu/Pin API. // 1 ms SysTick initialized in board.c diff --git a/ports/atmel-samd/boards/adafruit_pixel_trinkey_m0/mpconfigboard.mk b/ports/atmel-samd/boards/adafruit_pixel_trinkey_m0/mpconfigboard.mk index 32db497b010..24df3a1fbe1 100644 --- a/ports/atmel-samd/boards/adafruit_pixel_trinkey_m0/mpconfigboard.mk +++ b/ports/atmel-samd/boards/adafruit_pixel_trinkey_m0/mpconfigboard.mk @@ -19,6 +19,7 @@ CIRCUITPY_PWMIO = 1 CIRCUITPY_ROTARYIO = 0 CIRCUITPY_RTC = 0 CIRCUITPY_TOUCHIO = 0 +CIRCUITPY_USB_MIDI = 0 CIRCUITPY_PIXELBUF = 1 diff --git a/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk b/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk index 5c1cab422cb..32793c20d2f 100755 --- a/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk @@ -13,6 +13,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_I2CTARGET = 0 CIRCUITPY_PS2IO = 1 CIRCUITPY_JPEGIO = 0 +CIRCUITPY_MSGPACK = 0 CIRCUITPY_SPITARGET = 0 CIRCUITPY_SYNTHIO = 0 CIRCUITPY_TILEPALETTEMAPPER = 0 diff --git a/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk b/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk index 2ad140094b3..14744513bbc 100644 --- a/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk @@ -12,6 +12,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_I2CTARGET = 0 CIRCUITPY_JPEGIO = 0 +CIRCUITPY_MSGPACK = 0 CIRCUITPY_SPITARGET = 0 CIRCUITPY_SYNTHIO = 0 CIRCUITPY_TILEPALETTEMAPPER = 0 diff --git a/ports/atmel-samd/boards/hallowing_m0_express/board.c b/ports/atmel-samd/boards/hallowing_m0_express/board.c index fd5ad548512..4a0b727552c 100644 --- a/ports/atmel-samd/boards/hallowing_m0_express/board.c +++ b/ports/atmel-samd/boards/hallowing_m0_express/board.c @@ -28,7 +28,7 @@ uint8_t display_init_sequence[] = { 0xc3, 2, 0x8a, 0x2a, 0xc4, 2, 0x8a, 0xee, 0xc5, 1, 0x0e, // _VMCTR1 VCOMH = 4V, VOML = -1.1V - 0x2a, 0, // _INVOFF + 0x20, 0, // _INVOFF 0x36, 1, 0x18, // _MADCTL bottom to top refresh // 1 clk cycle nonoverlap, 2 cycle gate rise, 3 cycle osc equalie, // fix on VTL diff --git a/ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk b/ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk index ebe9403c34d..b20242a4361 100644 --- a/ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk +++ b/ports/atmel-samd/boards/kicksat-sprite/mpconfigboard.mk @@ -18,11 +18,13 @@ CIRCUITPY_AUDIOMIXER = 0 CIRCUITPY_AUDIOMP3 = 0 CIRCUITPY_BLEIO_HCI = 0 CIRCUITPY_DISPLAYIO = 0 +CIRCUITPY_ERRNO = 0 CIRCUITPY_FLOPPYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_PIXELMAP = 0 CIRCUITPY_GETPASS = 0 CIRCUITPY_KEYPAD = 0 +CIRCUITPY_LOAD_NATIVE = 0 CIRCUITPY_MSGPACK = 0 CIRCUITPY_PIXELBUF = 0 CIRCUITPY_PS2IO = 0 diff --git a/ports/atmel-samd/boards/openbook_m4/mpconfigboard.mk b/ports/atmel-samd/boards/openbook_m4/mpconfigboard.mk index 2701fd249f9..aae02a95325 100644 --- a/ports/atmel-samd/boards/openbook_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/openbook_m4/mpconfigboard.mk @@ -14,6 +14,7 @@ CIRCUITPY_FLOPPYIO = 0 CIRCUITPY_I2CTARGET = 0 CIRCUITPY_JPEGIO = 0 CIRCUITPY_KEYPAD = 1 +CIRCUITPY_MSGPACK = 0 CIRCUITPY_SYNTHIO = 0 CIRCUITPY_TERMINALIO_VT100 = 0 CIRCUITPY_TILEPALETTEMAPPER = 0 diff --git a/ports/atmel-samd/boards/pewpew_lcd/board.c b/ports/atmel-samd/boards/pewpew_lcd/board.c index d60efd39bf7..547962790ab 100644 --- a/ports/atmel-samd/boards/pewpew_lcd/board.c +++ b/ports/atmel-samd/boards/pewpew_lcd/board.c @@ -28,7 +28,7 @@ uint8_t display_init_sequence[] = { 0xc3, 2, 0x8a, 0x2a, 0xc4, 2, 0x8a, 0xee, 0xc5, 1, 0x0e, // _VMCTR1 VCOMH = 4V, VOML = -1.1V - 0x2a, 0, // _INVOFF + 0x20, 0, // _INVOFF 0x36, 1, 0xa0, // _MADCTL // 1 clk cycle nonoverlap, 2 cycle gate rise, 3 cycle osc equalie, // fix on VTL diff --git a/ports/atmel-samd/boards/pewpew_m4/board.c b/ports/atmel-samd/boards/pewpew_m4/board.c index ac67b82a3dc..bec685eadff 100644 --- a/ports/atmel-samd/boards/pewpew_m4/board.c +++ b/ports/atmel-samd/boards/pewpew_m4/board.c @@ -57,7 +57,7 @@ uint8_t display_init_sequence[] = { 0xc3, 2, 0x8a, 0x2a, 0xc4, 2, 0x8a, 0xee, 0xc5, 1, 0x0e, // _VMCTR1 VCOMH = 4V, VOML = -1.1V - 0x2a, 0, // _INVOFF + 0x20, 0, // _INVOFF 0x36, 1, 0xa0, // _MADCTL // 1 clk cycle nonoverlap, 2 cycle gate rise, 3 cycle osc equalie, // fix on VTL diff --git a/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk b/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk index 0f239fae93a..9874cc51667 100644 --- a/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk @@ -39,6 +39,7 @@ CIRCUITPY_BUSDEVICE = 0 CIRCUITPY_BITMAPFILTER = 0 CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_GIFIO = 0 +CIRCUITPY_LOAD_NATIVE = 0 CIRCUITPY_WATCHDOG = 0 CIRCUITPY_AUDIOIO = 1 diff --git a/ports/atmel-samd/boards/pybadge/board.c b/ports/atmel-samd/boards/pybadge/board.c index e938e96e19e..85f59fb084e 100644 --- a/ports/atmel-samd/boards/pybadge/board.c +++ b/ports/atmel-samd/boards/pybadge/board.c @@ -28,7 +28,7 @@ uint8_t display_init_sequence[] = { 0xc3, 2, 0x8a, 0x2a, 0xc4, 2, 0x8a, 0xee, 0xc5, 1, 0x0e, // _VMCTR1 VCOMH = 4V, VOML = -1.1V - 0x2a, 0, // _INVOFF + 0x20, 0, // _INVOFF 0x36, 1, 0b10100000, // _MADCTL for rotation 0 // 1 clk cycle nonoverlap, 2 cycle gate rise, 3 cycle osc equalie, // fix on VTL diff --git a/ports/atmel-samd/boards/pybadge/mpconfigboard.mk b/ports/atmel-samd/boards/pybadge/mpconfigboard.mk index 86ce2d47e8c..773c7709c00 100644 --- a/ports/atmel-samd/boards/pybadge/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pybadge/mpconfigboard.mk @@ -22,6 +22,18 @@ CIRCUITPY_PARALLELDISPLAYBUS= 0 CIRCUITPY_SPITARGET = 0 CIRCUITPY_STAGE = 1 +CIRCUITPY_PICOGAME = 1 +CIRCUITPY_PICOGAME_FAST_DISPLAY = 1 +CIRCUITPY_PICOGAME_RGB444 = 1 + +# Not enough room for picogame and these; all are only reachable through the Feather header. +CIRCUITPY_BLEIO_HCI = 0 +CIRCUITPY_FREQUENCYIO = 0 +CIRCUITPY_I2CTARGET = 0 +CIRCUITPY_PS2IO = 0 +CIRCUITPY_PULSEIO = 0 +CIRCUITPY_SDCARDIO = 0 + FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/pybadge # We don't have room for the fonts for terminalio for certain languages, diff --git a/ports/atmel-samd/boards/pygamer/board.c b/ports/atmel-samd/boards/pygamer/board.c index fcf6aff0e65..ba9c498cb7b 100644 --- a/ports/atmel-samd/boards/pygamer/board.c +++ b/ports/atmel-samd/boards/pygamer/board.c @@ -29,7 +29,7 @@ uint8_t display_init_sequence[] = { 0xc3, 2, 0x8a, 0x2a, 0xc4, 2, 0x8a, 0xee, 0xc5, 1, 0x0e, // _VMCTR1 VCOMH = 4V, VOML = -1.1V - 0x2a, 0, // _INVOFF + 0x20, 0, // _INVOFF 0x36, 1, 0b10100000, // _MADCTL for rotation 0 // 1 clk cycle nonoverlap, 2 cycle gate rise, 3 cycle osc equalie, // fix on VTL diff --git a/ports/atmel-samd/boards/silicognition-m4-shim/mpconfigboard.mk b/ports/atmel-samd/boards/silicognition-m4-shim/mpconfigboard.mk index 99c447b7a9d..a8b0677e759 100644 --- a/ports/atmel-samd/boards/silicognition-m4-shim/mpconfigboard.mk +++ b/ports/atmel-samd/boards/silicognition-m4-shim/mpconfigboard.mk @@ -12,6 +12,7 @@ LONGINT_IMPL = MPZ CIRCUITPY_I2CTARGET = 0 CIRCUITPY_JPEGIO = 0 +CIRCUITPY_MSGPACK = 0 CIRCUITPY_SPITARGET = 0 CIRCUITPY_SYNTHIO = 0 CIRCUITPY_TILEPALETTEMAPPER = 0 diff --git a/ports/atmel-samd/boards/ugame10/board.c b/ports/atmel-samd/boards/ugame10/board.c index 34f769e358f..fc2725afd8f 100644 --- a/ports/atmel-samd/boards/ugame10/board.c +++ b/ports/atmel-samd/boards/ugame10/board.c @@ -28,7 +28,7 @@ uint8_t display_init_sequence[] = { 0xc3, 2, 0x8a, 0x2a, 0xc4, 2, 0x8a, 0xee, 0xc5, 1, 0x0e, // _VMCTR1 VCOMH = 4V, VOML = -1.1V - 0x2a, 0, // _INVOFF + 0x20, 0, // _INVOFF 0x36, 1, 0xa8, // _MADCTL bottom to top refresh // 1 clk cycle nonoverlap, 2 cycle gate rise, 3 cycle osc equalie, // fix on VTL diff --git a/ports/atmel-samd/common-hal/busio/SPI.c b/ports/atmel-samd/common-hal/busio/SPI.c index 19fd81e425d..d9f1ac13561 100644 --- a/ports/atmel-samd/common-hal/busio/SPI.c +++ b/ports/atmel-samd/common-hal/busio/SPI.c @@ -154,6 +154,9 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, setup_pin(clock, clock_pinmux, GPIO_DIRECTION_OUT); self->clock_pin = clock->number; + #if CIRCUITPY_BUSIO_SPI_ASYNC + self->async_active = false; + #endif if (mosi_none) { self->MOSI_pin = NO_PIN; @@ -192,6 +195,9 @@ void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { if (common_hal_busio_spi_deinited(self)) { return; } + #if CIRCUITPY_BUSIO_SPI_ASYNC + common_hal_busio_spi_end(self); + #endif allow_reset_sercom(self->spi_desc.dev.prvt); spi_m_sync_disable(&self->spi_desc); @@ -285,6 +291,40 @@ bool common_hal_busio_spi_write(busio_spi_obj_t *self, return status >= 0; // Status is number of chars read or an error code < 0. } +#if CIRCUITPY_BUSIO_SPI_ASYNC +void common_hal_busio_spi_write_start(busio_spi_obj_t *self, const uint8_t *data, size_t len, + circuitpy_async_flag_t *done) { + common_hal_busio_spi_end(self); + CIRCUITPY_ASYNC_FLAG_INIT(done); + self->async_done = done; + // One descriptor holds at most 65535 beats; longer writes and short ones go the normal way. + if (len >= 32 && len <= 65535) { + Sercom *sercom = self->spi_desc.dev.prvt; + shared_dma_transfer_start(&self->async_xfer, sercom, data, &sercom->SPI.DATA.reg, + NULL, NULL, len, 0); + if (self->async_xfer.failure == 0) { + self->async_active = true; + return; + } + } + common_hal_busio_spi_write(self, data, len); + CIRCUITPY_ASYNC_FLAG_SET(done); +} + +void common_hal_busio_spi_end(busio_spi_obj_t *self) { + if (!self->async_active) { + return; + } + // finished() also clears the RX overflow a TX-only transfer leaves on the SERCOM. + // No background tasks here: the caller holds the bus, and one of them may want it. + while (!shared_dma_transfer_finished(&self->async_xfer)) { + } + shared_dma_transfer_close(&self->async_xfer); + self->async_active = false; + CIRCUITPY_ASYNC_FLAG_SET(self->async_done); +} +#endif + bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size_t len, uint8_t write_value) { if (len == 0) { diff --git a/ports/atmel-samd/common-hal/busio/SPI.h b/ports/atmel-samd/common-hal/busio/SPI.h index ed8bc220a34..57acd5581df 100644 --- a/ports/atmel-samd/common-hal/busio/SPI.h +++ b/ports/atmel-samd/common-hal/busio/SPI.h @@ -6,11 +6,16 @@ #pragma once +#include "py/mpconfig.h" +#if CIRCUITPY_BUSIO_SPI_ASYNC +#include "peripherals/samd/dma.h" +#endif #include "common-hal/microcontroller/Pin.h" #include "hal/include/hal_spi_m_sync.h" #include "py/obj.h" +#include "supervisor/shared/async_flag.h" typedef struct { mp_obj_base_t base; @@ -19,4 +24,9 @@ typedef struct { uint8_t clock_pin; uint8_t MOSI_pin; uint8_t MISO_pin; + #if CIRCUITPY_BUSIO_SPI_ASYNC + bool async_active; // a write_start DMA transfer may still be running + dma_transfer_t async_xfer; + circuitpy_async_flag_t *async_done; + #endif } busio_spi_obj_t; diff --git a/ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c b/ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c index 56eb82678ec..ad10d84351c 100644 --- a/ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c +++ b/ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c @@ -30,14 +30,14 @@ #define PIN_PCC_CLK (PIN_PA14) void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_parallelimagecapture_obj_t *self, - const uint8_t data_pins[], + const mcu_pin_obj_t **data_pins, uint8_t data_count, const mcu_pin_obj_t *data_clock, const mcu_pin_obj_t *vertical_sync, const mcu_pin_obj_t *horizontal_reference) { for (int i = 0; i < data_count; i++) { - if (data_pins[i] != PIN_PCC_D0 + i) { + if (common_hal_mcu_pin_number(data_pins[i]) != PIN_PCC_D0 + i) { mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid data_pins[%d]"), i); } } @@ -56,7 +56,7 @@ void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_paralle } // technically, 0 was validated as free already but check again for (int i = 0; i < data_count; i++) { - if (!pin_number_is_free(data_pins[i])) { + if (!pin_number_is_free(common_hal_mcu_pin_number(data_pins[i]))) { mp_raise_ValueError_varg(MP_ERROR_TEXT("data pin #%d in use"), i); } } diff --git a/ports/atmel-samd/common-hal/microcontroller/Pin.c b/ports/atmel-samd/common-hal/microcontroller/Pin.c index bbf3957818e..586ce156cb2 100644 --- a/ports/atmel-samd/common-hal/microcontroller/Pin.c +++ b/ports/atmel-samd/common-hal/microcontroller/Pin.c @@ -115,13 +115,6 @@ void common_hal_never_reset_pin(const mcu_pin_obj_t* pin) { never_reset_pin_number(pin->number); } -void common_hal_reset_pin(const mcu_pin_obj_t* pin) { - if (pin == NULL) { - return; - } - reset_pin_number(pin->number); -} - void claim_pin(const mcu_pin_obj_t* pin) { #ifdef SPEAKER_ENABLE_PIN if (pin == SPEAKER_ENABLE_PIN) { @@ -168,10 +161,6 @@ uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t* pin) { return pin->number; } -void common_hal_mcu_pin_claim(const mcu_pin_obj_t* pin) { - return claim_pin(pin); -} - void common_hal_mcu_pin_reset_number(uint8_t pin_no) { reset_pin_number(pin_no); } diff --git a/ports/atmel-samd/common-hal/microcontroller/Pin.h b/ports/atmel-samd/common-hal/microcontroller/Pin.h index cce9d84ef33..b804ac2ef6c 100644 --- a/ports/atmel-samd/common-hal/microcontroller/Pin.h +++ b/ports/atmel-samd/common-hal/microcontroller/Pin.h @@ -18,6 +18,8 @@ void never_reset_pin_number(uint8_t pin_number); void claim_pin(const mcu_pin_obj_t *pin); bool pin_number_is_free(uint8_t pin_number); +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); + typedef struct { const mcu_pin_obj_t *obj; uint8_t instance; diff --git a/ports/atmel-samd/common-hal/picogame/Display.c b/ports/atmel-samd/common-hal/picogame/Display.c new file mode 100644 index 00000000000..b3a1e328069 --- /dev/null +++ b/ports/atmel-samd/common-hal/picogame/Display.c @@ -0,0 +1,153 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Vladimir Smitka +// +// SPDX-License-Identifier: MIT + +#include "common-hal/picogame/Display.h" + +#include "py/runtime.h" +#include "shared-module/picogame/__init__.h" +#include "shared-module/displayio/display_core.h" +#include "shared-bindings/displayio/__init__.h" +#include "shared-bindings/fourwire/FourWire.h" + +#include "peripherals/samd/dma.h" + +// shared_dma_transfer_start truncates the beat count to 16 bits, so one descriptor tops out here. +#define PICOGAME_DMA_MAX_BYTES 65535 + +void common_hal_picogame_display_construct(picogame_display_obj_t *self, + busdisplay_busdisplay_obj_t *display, bool rgb444) { + self->display = display; + #if CIRCUITPY_PICOGAME_RGB444 + self->rgb444 = rgb444; + #else + if (rgb444) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("Operation or feature not supported")); + } + self->rgb444 = false; + #endif + + // The fast path needs the raw SERCOM; only FourWire SPI buses are supported. + if (!mp_obj_is_type(display->bus.bus, &fourwire_fourwire_type)) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q, not %q"), + MP_QSTR_display, MP_QSTR_FourWire, mp_obj_get_type(display->bus.bus)->name); + } + fourwire_fourwire_obj_t *fw = MP_OBJ_TO_PTR(display->bus.bus); + self->sercom = (Sercom *)fw->bus->spi_desc.dev.prvt; + + #if CIRCUITPY_PICOGAME_RGB444 + // COLMOD; also resets a panel left in the other format by a previous program. + picogame_set_pixel_format(display, rgb444); + #endif + +} + +// Strips go through shared_dma_transfer_*, which handles the DMAC start errata and the RX +// overflow a TX-only transfer leaves on the SERCOM. +static void dma_finish(dma_transfer_t *xfer) { + // Bounded wait: a strip takes well under 1 ms, so a timeout tears one frame instead of + // hanging the board. close() releases the channel either way. + for (uint32_t spins = 0; spins < 2000000; spins++) { + if (shared_dma_transfer_finished(xfer)) { + break; + } + } + shared_dma_transfer_close(xfer); +} + +void common_hal_picogame_display_render(picogame_display_obj_t *self, + mp_obj_t *items, uint8_t *kinds, size_t n, + uint16_t *buf_a, uint16_t *buf_b, size_t buf_pixels, + int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t background, + int ox, int oy) { + + busdisplay_busdisplay_obj_t *display = self->display; + Sercom *sercom = self->sercom; + + // RGB444 packs 2 px into 3 bytes, so widen the region to even x bounds. + #if CIRCUITPY_PICOGAME_RGB444 + if (self->rgb444) { + x0 &= ~1; + x1 = (x1 + 1) & ~1; + if (x1 > display->core.width) { + x1 = display->core.width; + } + } + #endif + + // Sets the window and starts the transaction; the strips follow as data. + int region_w, strip_h; + int cx0 = x0, cy0 = y0, cx1 = x1, cy1 = y1; // clamped to the panel by strip_begin + if (!picogame_strip_begin(display, &cx0, &cy0, &cx1, &cy1, buf_pixels, ®ion_w, &strip_h)) { + return; + } + + uint16_t *bufs[2] = { buf_a, buf_b }; + int cur = 0; + bool first = true; + dma_transfer_t xfer; + bool xfer_active = false; + #if CIRCUITPY_PICOGAME_RGB444 + const bool rgb444 = self->rgb444; + #endif + + // An exception from a StripDraw callback is re-raised after the DMA finishes and the + // transaction ends. + mp_obj_t pending = MP_OBJ_NULL; + + for (int sy = cy0; sy < cy1; sy += strip_h) { + int sh = picogame_imin(strip_h, cy1 - sy); + uint16_t *buf = bufs[cur]; + + // Composes into the buffer the in-flight DMA is not reading. + pending = picogame_blit_strip_layers(buf, region_w, sy, sh, cx0, items, kinds, n, background, ox, oy); + + // Packed in place, 3/4 the bytes. + #if CIRCUITPY_PICOGAME_RGB444 + size_t nbytes = rgb444 + ? picogame_pack_rgb444(buf, (size_t)region_w * sh) + : (size_t)region_w * sh * 2; + #else + size_t nbytes = (size_t)region_w * sh * 2; + #endif + + if (xfer_active) { + dma_finish(&xfer); + xfer_active = false; + } + + if (first || nbytes > PICOGAME_DMA_MAX_BYTES) { + // The first strip sets DC for data through busdisplay; the DMA strips that follow + // leave it. Strips over the descriptor limit also go this way (busio splits them). + display->bus.send(display->bus.bus, DISPLAY_DATA, + CHIP_SELECT_UNTOUCHED, (uint8_t *)buf, nbytes); + first = false; + } else { + shared_dma_transfer_start(&xfer, sercom, (const uint8_t *)buf, + &sercom->SPI.DATA.reg, NULL, NULL, nbytes, 0); + if (xfer.failure != 0) { // no channel free + shared_dma_transfer_close(&xfer); + display->bus.send(display->bus.bus, DISPLAY_DATA, + CHIP_SELECT_UNTOUCHED, (uint8_t *)buf, nbytes); + } else { + xfer_active = true; + } + } + cur ^= 1; + if (pending != MP_OBJ_NULL) { + break; + } + } + + if (xfer_active) { + dma_finish(&xfer); + } + + displayio_display_bus_end_transaction(&display->bus); + + if (pending != MP_OBJ_NULL) { + nlr_raise(MP_OBJ_TO_PTR(pending)); + } +} diff --git a/ports/atmel-samd/common-hal/picogame/Display.h b/ports/atmel-samd/common-hal/picogame/Display.h new file mode 100644 index 00000000000..89546e2c7f0 --- /dev/null +++ b/ports/atmel-samd/common-hal/picogame/Display.h @@ -0,0 +1,37 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Vladimir Smitka +// +// SPDX-License-Identifier: MIT +// +// Fast display backend (atmel-samd): wraps an existing busdisplay and streams pixels with a DMAC +// channel of our own, double-buffered so the CPU blits the next strip while the current one is on +// the wire. Reuses the busdisplay's SERCOM, window opcodes and dimensions -- controller and +// resolution agnostic. +// +// Why this pays on a SAMD51: busio already sends through the DMAC, but it busy-waits for each +// transfer to land, so the compose time is added to the wire time instead of hidden under it. + +#pragma once + +#include "py/obj.h" + +#include "include/sam.h" +#include "shared-bindings/busdisplay/BusDisplay.h" +#include "shared-module/picogame/Sprite.h" + +typedef struct { + mp_obj_base_t base; + busdisplay_busdisplay_obj_t *display; + Sercom *sercom; // the busdisplay's SERCOM; strips go out on the port's shared DMA + bool rgb444; +} picogame_display_obj_t; + +void common_hal_picogame_display_construct(picogame_display_obj_t *self, + busdisplay_busdisplay_obj_t *display, bool rgb444); + +void common_hal_picogame_display_render(picogame_display_obj_t *self, + mp_obj_t *items, uint8_t *kinds, size_t n, + uint16_t *buf_a, uint16_t *buf_b, size_t buf_pixels, + int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t background, + int ox, int oy); diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index f846f490aa4..29a881d2d98 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -98,6 +98,10 @@ endif # samd21 # Put samx5x-only choices here. ifneq ($(filter $(CHIP_FAMILY),samd51 same51 same54),) +CIRCUITPY_BUSIO_SPI_ASYNC ?= 1 + +# The 24 KB stack has room for a larger display refresh buffer. +CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE ?= 2048 # No native touchio on SAMx5x. CIRCUITPY_TOUCHIO_USE_NATIVE = 0 @@ -121,13 +125,17 @@ CIRCUITPY_ALARM ?= 1 CIRCUITPY_BITMAPFILTER ?= 0 CIRCUITPY_FLOPPYIO ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_FRAMEBUFFERIO ?= $(CIRCUITPY_FULL_BUILD) +CIRCUITPY_LOAD_NATIVE ?= 1 CIRCUITPY_MAX3421E ?= $(HAS_1MB_FLASH) CIRCUITPY_PS2IO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FRAMEBUFFERIO) CIRCUITPY_SAMD ?= 1 CIRCUITPY_SPITARGET ?= 1 CIRCUITPY_SYNTHIO_MAX_CHANNELS = 12 +# Trade ulab speed for flash only where flash is tight. +ifeq ($(HAS_1MB_FLASH),0) CIRCUITPY_ULAB_OPTIMIZE_SIZE ?= 1 +endif CIRCUITPY_WATCHDOG ?= 1 ifeq ($(CHIP_VARIANT),SAMD51G19A) diff --git a/ports/broadcom/Makefile b/ports/broadcom/Makefile deleted file mode 100644 index dda93c6fd7c..00000000000 --- a/ports/broadcom/Makefile +++ /dev/null @@ -1,158 +0,0 @@ -# This file is part of the CircuitPython project: https://circuitpython.org -# -# SPDX-FileCopyrightText: Copyright (c) 2020 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -include ../../py/circuitpy_mkenv.mk - -ifeq ($(CHIP_VARIANT), "bcm2711") -CFLAGS += -mcpu=cortex-a72 -DBCM_VERSION=2711 -CROSS_COMPILE = aarch64-none-elf- -SUFFIX = 8 -else ifeq ($(CHIP_VARIANT), "bcm2837") -CFLAGS += -mcpu=cortex-a53 -DBCM_VERSION=2837 -CROSS_COMPILE = aarch64-none-elf- -SUFFIX = 8 -else ifeq ($(CHIP_VARIANT), "bcm2835") -CFLAGS += -mcpu=arm1176jzf-s -DBCM_VERSION=2835 -CROSS_COMPILE = arm-none-eabi- -SUFFIX = -# TODO add 32-bit support for Cortex-A7 in 2836 -endif - -INC += -I. \ - -I../.. \ - -I../../lib/mp-readline \ - -I../../lib/timeutils \ - -I../../lib/sdmmc/include \ - -Iboards/$(BOARD) \ - -Iboards/ \ - -Iperipherals/ \ - -I../../lib/tinyusb/src \ - -I../../supervisor/shared/usb \ - -I$(BUILD) - - -SRC_C += bindings/videocore/__init__.c \ - bindings/videocore/Framebuffer.c \ - boards/$(BOARD)/board.c \ - boards/$(BOARD)/pins.c \ - background.c \ - common-hal/videocore/Framebuffer.c \ - mphalport.c \ - lib/sdmmc/sdmmc_cmd.c \ - lib/sdmmc/sdmmc_common.c \ - lib/sdmmc/sdmmc_init.c \ - lib/sdmmc/sdmmc_io.c \ - lib/sdmmc/sdmmc_mmc.c \ - lib/sdmmc/sdmmc_sd.c \ - lib/tinyusb/src/portable/synopsys/dwc2/dcd_dwc2.c \ - lib/tinyusb/src/portable/synopsys/dwc2/dwc2_common.c \ - peripherals/broadcom/caches.c \ - peripherals/broadcom/gen/interrupt_handlers.c \ - peripherals/broadcom/gen/pins.c \ - peripherals/broadcom/gpio.c \ - peripherals/broadcom/interrupts.c \ - peripherals/broadcom/mmu.c \ - peripherals/broadcom/vcmailbox.c - -SRC_S = peripherals/broadcom/boot$(SUFFIX).s - -SRC_C += shared/runtime/gchelper_generic.c - -OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) - ifeq ($(INTERNAL_LIBM),1) -OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) -endif -OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) - -# BCM CLFAGS -CFLAGS += -nostartfiles -DMICROPY_HW_MCU_NAME="\"$(CHIP_VARIANT)\"" - - -OPTIMIZATION_FLAGS ?= -O3 -CFLAGS += $(OPTIMIZATION_FLAGS) - -# TinyUSB defines -CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_BCM2711 -DCFG_TUD_MIDI_RX_BUFSIZE=512 \ - -DCFG_TUD_CDC_RX_BUFSIZE=640 -DCFG_TUD_MIDI_TX_BUFSIZE=512 \ - -DCFG_TUD_CDC_TX_BUFSIZE=512 -DCFG_TUD_MSC_BUFSIZE=1024 - -#Debugging/Optimization -ifeq ($(DEBUG), 1) - CFLAGS += -ggdb3 -Og - # No LTO because we may place some functions in RAM instead of flash. -else - CFLAGS += -DNDEBUG -ggdb3 - - # No LTO because we may place some functions in RAM instead of flash. - - ifdef CFLAGS_BOARD - CFLAGS += $(CFLAGS_BOARD) - endif -endif - - -CFLAGS += $(INC) -Wall -Werror -std=gnu11 $(BASE_CFLAGS) $(CFLAGS_MOD) $(COPT) $(DISABLE_WARNINGS) -Werror=old-style-definition - -$(BUILD)/lib/tlsf/tlsf.o: CFLAGS += -Wno-cast-align - -SRC_QSTR += $(SRC_C) $(SRC_SUPERVISOR) $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED) $(SRC_CIRCUITPY_COMMON) - -LDFLAGS += $(CFLAGS) -T peripherals/broadcom/link$(SUFFIX).ld -Wl,--gc-sections -Wl,-Map=$@.map # -Wl,--cref - -# Use toolchain libm if we're not using our own. -ifndef INTERNAL_LIBM -LIBS += -lm -endif - -all: $(BUILD)/firmware.kernel$(SUFFIX).img $(BUILD)/firmware.disk.img.zip - -$(BUILD)/lib/tlsf/tlsf.o: CFLAGS += -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast - -%.o: %.c - $(CC) $(CFLAGS) -c $< -o $@ - -ifeq ($(VALID_BOARD),) -$(BUILD)/kernel$(SUFFIX).elf: invalid-board -else -$(BUILD)/kernel$(SUFFIX).elf: $(OBJ) - $(STEPECHO) "LINK $@" - $(Q)echo $(OBJ) > $(BUILD)/firmware.objs - $(Q)$(CC) -o $@ $(LDFLAGS) @$(BUILD)/firmware.objs -Wl,--print-memory-usage -Wl,--start-group $(LIBS) -Wl,--end-group -endif - -$(BUILD)/kernel$(SUFFIX).img: $(BUILD)/kernel$(SUFFIX).elf - $(STEPECHO) "Create $@" - $(OBJCOPY) -O binary $(BUILD)/kernel$(SUFFIX).elf $@ - -$(BUILD)/firmware.kernel$(SUFFIX).img: $(BUILD)/kernel$(SUFFIX).img - $(STEPECHO) "Create $@" - $(CP) $^ $@ - -$(BUILD)/firmware.disk.img.zip: $(BUILD)/kernel$(SUFFIX).img - $(STEPECHO) "Create $@" - $(Q)dd if=/dev/zero of=$(BUILD)/circuitpython-disk.img bs=1 count=0 seek=256M - $(Q)parted -s $(BUILD)/circuitpython-disk.img mktable msdos - $(Q)parted -s $(BUILD)/circuitpython-disk.img mkpart primary fat32 0% 100% - $(Q)mkfs.fat -F 32 -n BOOT --offset=2048 $(BUILD)/circuitpython-disk.img - - $(Q)mcopy -i $(BUILD)/circuitpython-disk.img@@1M config.txt firmware/bootcode.bin firmware/fixup* firmware/start* firmware/*.dtb :: - $(Q)mcopy -i $(BUILD)/circuitpython-disk.img@@1M $(BUILD)/kernel$(SUFFIX).img :: - $(Q)zip $@ $(BUILD)/circuitpython-disk.img - $(Q)rm $(BUILD)/circuitpython-disk.img - -.PHONY: $(BUILD)/rpiboot rpiboot -rpiboot: $(BUILD)/rpiboot -$(BUILD)/rpiboot: $(BUILD)/kernel$(SUFFIX).img - mkdir -vp $@ - cp -vf $(BUILD)/kernel$(SUFFIX).img $@/ - cp -vfr config.txt firmware/bootcode.bin firmware/fixup* firmware/start* firmware/*.dtb $@/ - #sed -i -e "s/BOOT_UART=0/BOOT_UART=1/" $@/bootcode.bin $@/bootcode.bin - echo uart_2ndstage=1 >> $@/config.txt - -include $(TOP)/py/mkrules.mk diff --git a/ports/broadcom/README.rst b/ports/broadcom/README.rst deleted file mode 100644 index 10ff81711bd..00000000000 --- a/ports/broadcom/README.rst +++ /dev/null @@ -1,5 +0,0 @@ -Broadcom -================== - -This port supports running CircuitPython bare-metal on Raspberry Pi single board -computers that utilize Broadcom system-on-chips. diff --git a/ports/broadcom/background.c b/ports/broadcom/background.c deleted file mode 100644 index 5bb961eea59..00000000000 --- a/ports/broadcom/background.c +++ /dev/null @@ -1,20 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT -#include "background.h" - -#include "py/runtime.h" -#include "supervisor/port.h" - -void port_start_background_tick(void) { -} -void port_finish_background_tick(void) { -} - -void port_background_tick(void) { -} - -void port_background_task(void) { -} diff --git a/ports/broadcom/background.h b/ports/broadcom/background.h deleted file mode 100644 index fe71149ab4b..00000000000 --- a/ports/broadcom/background.h +++ /dev/null @@ -1,9 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include diff --git a/ports/broadcom/bindings/videocore/Framebuffer.c b/ports/broadcom/bindings/videocore/Framebuffer.c deleted file mode 100644 index 66135baa7fe..00000000000 --- a/ports/broadcom/bindings/videocore/Framebuffer.c +++ /dev/null @@ -1,163 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/obj.h" -#include "py/objproperty.h" -#include "py/runtime.h" -#include "py/objarray.h" - -#include "bindings/videocore/Framebuffer.h" -#include "shared-bindings/util.h" -#include "shared-module/displayio/__init__.h" -#include "shared-module/framebufferio/__init__.h" -#include "shared-module/framebufferio/FramebufferDisplay.h" - -//| class Framebuffer: -//| """A VideoCore managed frame buffer.""" -//| -//| def __init__(self, width: int, height: int) -> None: -//| """Create a Framebuffer object with the given dimensions. Memory is -//| allocated outside of the heap in GPU memory. -//| -//| The framebuffer is in "ARGB8888" format. -//| -//| A Framebuffer is often used in conjunction with a -//| `framebufferio.FramebufferDisplay`.""" -//| - -static mp_obj_t videocore_framebuffer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_width, ARG_height, }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_width, MP_ARG_INT | MP_ARG_REQUIRED }, - { MP_QSTR_height, MP_ARG_INT | MP_ARG_REQUIRED }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - videocore_framebuffer_obj_t *self = &allocate_display_bus_or_raise()->videocore; - self->base.type = &videocore_framebuffer_type; - - mp_uint_t width = (mp_uint_t)mp_arg_validate_int_min(args[ARG_width].u_int, 0, MP_QSTR_width); - mp_uint_t height = (mp_uint_t)mp_arg_validate_int_min(args[ARG_height].u_int, 0, MP_QSTR_height); - common_hal_videocore_framebuffer_construct(self, width, height); - - return MP_OBJ_FROM_PTR(self); -} - -//| def deinit(self) -> None: -//| """Free the resources (pins, timers, etc.) associated with this -//| rgbmatrix instance. After deinitialization, no further operations -//| may be performed.""" -//| ... -//| -static mp_obj_t videocore_framebuffer_deinit(mp_obj_t self_in) { - videocore_framebuffer_obj_t *self = (videocore_framebuffer_obj_t *)self_in; - common_hal_videocore_framebuffer_deinit(self); - return mp_const_none; -} - -static MP_DEFINE_CONST_FUN_OBJ_1(videocore_framebuffer_deinit_obj, videocore_framebuffer_deinit); - -static void check_for_deinit(videocore_framebuffer_obj_t *self) { - if (common_hal_videocore_framebuffer_deinited(self)) { - raise_deinited_error(); - } -} - -//| width: int -//| """The width of the display, in pixels""" -static mp_obj_t videocore_framebuffer_get_width(mp_obj_t self_in) { - videocore_framebuffer_obj_t *self = (videocore_framebuffer_obj_t *)self_in; - check_for_deinit(self); - return MP_OBJ_NEW_SMALL_INT(common_hal_videocore_framebuffer_get_width(self)); -} -MP_DEFINE_CONST_FUN_OBJ_1(videocore_framebuffer_get_width_obj, videocore_framebuffer_get_width); -MP_PROPERTY_GETTER(videocore_framebuffer_width_obj, - (mp_obj_t)&videocore_framebuffer_get_width_obj); - -//| height: int -//| """The height of the display, in pixels""" -//| -//| -static mp_obj_t videocore_framebuffer_get_height(mp_obj_t self_in) { - videocore_framebuffer_obj_t *self = (videocore_framebuffer_obj_t *)self_in; - check_for_deinit(self); - return MP_OBJ_NEW_SMALL_INT(common_hal_videocore_framebuffer_get_height(self)); -} -MP_DEFINE_CONST_FUN_OBJ_1(videocore_framebuffer_get_height_obj, videocore_framebuffer_get_height); - -MP_PROPERTY_GETTER(videocore_framebuffer_height_obj, - (mp_obj_t)&videocore_framebuffer_get_height_obj); - -static const mp_rom_map_elem_t videocore_framebuffer_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&videocore_framebuffer_deinit_obj) }, - - { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&videocore_framebuffer_width_obj) }, - { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&videocore_framebuffer_height_obj) }, -}; -static MP_DEFINE_CONST_DICT(videocore_framebuffer_locals_dict, videocore_framebuffer_locals_dict_table); - -static void videocore_framebuffer_get_bufinfo(mp_obj_t self_in, mp_buffer_info_t *bufinfo) { - common_hal_videocore_framebuffer_get_buffer(self_in, bufinfo, 0); -} - -// These versions exist so that the prototype matches the protocol, -// avoiding a type cast that can hide errors -static void videocore_framebuffer_swapbuffers(mp_obj_t self_in, uint8_t *dirty_row_bitmap) { - (void)dirty_row_bitmap; - common_hal_videocore_framebuffer_refresh(self_in); -} - -static void videocore_framebuffer_deinit_proto(mp_obj_t self_in) { - common_hal_videocore_framebuffer_deinit(self_in); -} - -static int videocore_framebuffer_get_width_proto(mp_obj_t self_in) { - return common_hal_videocore_framebuffer_get_width(self_in); -} - -static int videocore_framebuffer_get_height_proto(mp_obj_t self_in) { - return common_hal_videocore_framebuffer_get_height(self_in); -} - -static int videocore_framebuffer_get_color_depth_proto(mp_obj_t self_in) { - return 32; -} - -static int videocore_framebuffer_get_bytes_per_cell_proto(mp_obj_t self_in) { - return 1; -} - -static int videocore_framebuffer_get_native_frames_per_second_proto(mp_obj_t self_in) { - return 30; -} - -static int videocore_framebuffer_get_row_stride_proto(mp_obj_t self_in) { - return common_hal_videocore_framebuffer_get_row_stride(self_in); -} - -static const framebuffer_p_t videocore_framebuffer_proto = { - MP_PROTO_IMPLEMENT(MP_QSTR_protocol_framebuffer) - .get_bufinfo = videocore_framebuffer_get_bufinfo, - .get_width = videocore_framebuffer_get_width_proto, - .get_height = videocore_framebuffer_get_height_proto, - .get_color_depth = videocore_framebuffer_get_color_depth_proto, - .get_row_stride = videocore_framebuffer_get_row_stride_proto, - .get_bytes_per_cell = videocore_framebuffer_get_bytes_per_cell_proto, - .get_native_frames_per_second = videocore_framebuffer_get_native_frames_per_second_proto, - .swapbuffers = videocore_framebuffer_swapbuffers, - .deinit = videocore_framebuffer_deinit_proto, -}; - -MP_DEFINE_CONST_OBJ_TYPE( - videocore_framebuffer_type, - MP_QSTR_Framebuffer, - MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, - locals_dict, (mp_obj_dict_t *)&videocore_framebuffer_locals_dict, - make_new, videocore_framebuffer_make_new, - buffer, common_hal_videocore_framebuffer_get_buffer, - protocol, &videocore_framebuffer_proto - ); diff --git a/ports/broadcom/bindings/videocore/Framebuffer.h b/ports/broadcom/bindings/videocore/Framebuffer.h deleted file mode 100644 index cb6c04a67bc..00000000000 --- a/ports/broadcom/bindings/videocore/Framebuffer.h +++ /dev/null @@ -1,20 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2020 Jeff Epler for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "common-hal/videocore/Framebuffer.h" - -extern const mp_obj_type_t videocore_framebuffer_type; - -void common_hal_videocore_framebuffer_construct(videocore_framebuffer_obj_t *self, mp_uint_t width, mp_uint_t height); -void common_hal_videocore_framebuffer_deinit(videocore_framebuffer_obj_t *self); -bool common_hal_videocore_framebuffer_deinited(videocore_framebuffer_obj_t *self); -void common_hal_videocore_framebuffer_refresh(videocore_framebuffer_obj_t *self); -int common_hal_videocore_framebuffer_get_width(videocore_framebuffer_obj_t *self); -int common_hal_videocore_framebuffer_get_height(videocore_framebuffer_obj_t *self); -int common_hal_videocore_framebuffer_get_row_stride(videocore_framebuffer_obj_t *self); -mp_int_t common_hal_videocore_framebuffer_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags); diff --git a/ports/broadcom/bindings/videocore/__init__.c b/ports/broadcom/bindings/videocore/__init__.c deleted file mode 100644 index 3c83ba2ac78..00000000000 --- a/ports/broadcom/bindings/videocore/__init__.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include - -#include "py/obj.h" -#include "py/runtime.h" - -#include "bindings/videocore/Framebuffer.h" - -//| """Low-level routines for interacting with the Broadcom VideoCore GPU""" - -static const mp_rom_map_elem_t videocore_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_videocore) }, - { MP_ROM_QSTR(MP_QSTR_Framebuffer), MP_ROM_PTR(&videocore_framebuffer_type) }, -}; - -static MP_DEFINE_CONST_DICT(videocore_module_globals, videocore_module_globals_table); - -const mp_obj_module_t videocore_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&videocore_module_globals, -}; - -MP_REGISTER_MODULE(MP_QSTR_videocore, videocore_module); diff --git a/ports/broadcom/boards/diodes_delight_piunora/board.c b/ports/broadcom/boards/diodes_delight_piunora/board.c deleted file mode 100644 index 612d93776df..00000000000 --- a/ports/broadcom/boards/diodes_delight_piunora/board.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/board.h" -#include "mpconfigboard.h" - -#include "bindings/videocore/Framebuffer.h" -#include "shared-module/displayio/__init__.h" -#include "shared-bindings/framebufferio/FramebufferDisplay.h" - -void board_init(void) { - videocore_framebuffer_obj_t *fb = &allocate_display_bus()->videocore; - fb->base.type = &videocore_framebuffer_type; - common_hal_videocore_framebuffer_construct(fb, 640, 480); - - framebufferio_framebufferdisplay_obj_t *display = &allocate_display()->framebuffer_display; - display->base.type = &framebufferio_framebufferdisplay_type; - common_hal_framebufferio_framebufferdisplay_construct( - display, - MP_OBJ_FROM_PTR(fb), - 0, - true); -} - -// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/broadcom/boards/diodes_delight_piunora/mpconfigboard.h b/ports/broadcom/boards/diodes_delight_piunora/mpconfigboard.h deleted file mode 100644 index 298a94316ad..00000000000 --- a/ports/broadcom/boards/diodes_delight_piunora/mpconfigboard.h +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_HW_BOARD_NAME "Diodes Delight Piunora" - -#define DEFAULT_I2C_BUS_SCL (&pin_GPIO3) -#define DEFAULT_I2C_BUS_SDA (&pin_GPIO2) - -#define MICROPY_HW_NEOPIXEL (&pin_GPIO12) - -#define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO4) -#define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO5) diff --git a/ports/broadcom/boards/diodes_delight_piunora/mpconfigboard.mk b/ports/broadcom/boards/diodes_delight_piunora/mpconfigboard.mk deleted file mode 100644 index d05bb160282..00000000000 --- a/ports/broadcom/boards/diodes_delight_piunora/mpconfigboard.mk +++ /dev/null @@ -1,6 +0,0 @@ -USB_VID = 0x1209 -USB_PID = 0xD10D -USB_PRODUCT = "Piunora" -USB_MANUFACTURER = "Diodes Delight" - -CHIP_VARIANT = "bcm2711" diff --git a/ports/broadcom/boards/diodes_delight_piunora/pins.c b/ports/broadcom/boards/diodes_delight_piunora/pins.c deleted file mode 100644 index 27bbe93e1ee..00000000000 --- a/ports/broadcom/boards/diodes_delight_piunora/pins.c +++ /dev/null @@ -1,77 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -#include "shared-module/displayio/__init__.h" - -static const mp_rom_map_elem_t board_global_dict_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - - { MP_ROM_QSTR(MP_QSTR_GPIO5), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO5) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO4), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO4) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO18), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO18) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO19), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO19) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO20), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO20) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO21), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO21) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO22), MP_ROM_PTR(&pin_GPIO22) }, - { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO22) }, - { MP_ROM_QSTR(MP_QSTR_SDA6), MP_ROM_PTR(&pin_GPIO22) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO23), MP_ROM_PTR(&pin_GPIO23) }, - { MP_ROM_QSTR(MP_QSTR_D7), MP_ROM_PTR(&pin_GPIO23) }, - { MP_ROM_QSTR(MP_QSTR_SCL6), MP_ROM_PTR(&pin_GPIO23) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO6), MP_ROM_PTR(&pin_GPIO6) }, - { MP_ROM_QSTR(MP_QSTR_D8), MP_ROM_PTR(&pin_GPIO6) }, - { MP_ROM_QSTR(MP_QSTR_SDA4), MP_ROM_PTR(&pin_GPIO6) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO7), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_SCL4), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_SPI0_CE1), MP_ROM_PTR(&pin_GPIO7) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO8), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_SPI0_CE0), MP_ROM_PTR(&pin_GPIO8) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO10), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_SPI0_MOSI), MP_ROM_PTR(&pin_GPIO10) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO9), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_SPI0_MISO), MP_ROM_PTR(&pin_GPIO9) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO11), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SPI0_SCLK), MP_ROM_PTR(&pin_GPIO11) }, - - { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_SDA1), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO3) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO12), MP_ROM_PTR(&pin_GPIO12) }, - { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO12) }, - - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, - { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display)}, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/broadcom/boards/raspberrypi_cm4/board.c b/ports/broadcom/boards/raspberrypi_cm4/board.c deleted file mode 100644 index 612d93776df..00000000000 --- a/ports/broadcom/boards/raspberrypi_cm4/board.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/board.h" -#include "mpconfigboard.h" - -#include "bindings/videocore/Framebuffer.h" -#include "shared-module/displayio/__init__.h" -#include "shared-bindings/framebufferio/FramebufferDisplay.h" - -void board_init(void) { - videocore_framebuffer_obj_t *fb = &allocate_display_bus()->videocore; - fb->base.type = &videocore_framebuffer_type; - common_hal_videocore_framebuffer_construct(fb, 640, 480); - - framebufferio_framebufferdisplay_obj_t *display = &allocate_display()->framebuffer_display; - display->base.type = &framebufferio_framebufferdisplay_type; - common_hal_framebufferio_framebufferdisplay_construct( - display, - MP_OBJ_FROM_PTR(fb), - 0, - true); -} - -// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/broadcom/boards/raspberrypi_cm4/mpconfigboard.h b/ports/broadcom/boards/raspberrypi_cm4/mpconfigboard.h deleted file mode 100644 index 23b99342fac..00000000000 --- a/ports/broadcom/boards/raspberrypi_cm4/mpconfigboard.h +++ /dev/null @@ -1,9 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_HW_BOARD_NAME "Raspberry Pi Compute Module 4" diff --git a/ports/broadcom/boards/raspberrypi_cm4/mpconfigboard.mk b/ports/broadcom/boards/raspberrypi_cm4/mpconfigboard.mk deleted file mode 100644 index ee54efe84be..00000000000 --- a/ports/broadcom/boards/raspberrypi_cm4/mpconfigboard.mk +++ /dev/null @@ -1,6 +0,0 @@ -USB_VID = 0x2E8A -USB_PID = 0x1014 -USB_PRODUCT = "Compute Module 4" -USB_MANUFACTURER = "Raspberry Pi" - -CHIP_VARIANT = "bcm2711" diff --git a/ports/broadcom/boards/raspberrypi_cm4/pins.c b/ports/broadcom/boards/raspberrypi_cm4/pins.c deleted file mode 100644 index 83f781eea97..00000000000 --- a/ports/broadcom/boards/raspberrypi_cm4/pins.c +++ /dev/null @@ -1,54 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -#include "shared-module/displayio/__init__.h" - -static const mp_rom_map_elem_t board_global_dict_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - - // These match the names used in the CM4 datasheet - { MP_ROM_QSTR(MP_QSTR_ID_SD), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_ID_SC), MP_ROM_PTR(&pin_GPIO1) }, - { MP_ROM_QSTR(MP_QSTR_GPIO2), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_GPIO3), MP_ROM_PTR(&pin_GPIO3) }, - { MP_ROM_QSTR(MP_QSTR_GPIO4), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_GPIO5), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_GPIO6), MP_ROM_PTR(&pin_GPIO6) }, - { MP_ROM_QSTR(MP_QSTR_GPIO7), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_GPIO8), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_GPIO9), MP_ROM_PTR(&pin_GPIO9) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO10), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_GPIO11), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_GPIO12), MP_ROM_PTR(&pin_GPIO12) }, - { MP_ROM_QSTR(MP_QSTR_GPIO13), MP_ROM_PTR(&pin_GPIO13) }, - { MP_ROM_QSTR(MP_QSTR_GPIO14), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_GPIO15), MP_ROM_PTR(&pin_GPIO15) }, - - { MP_ROM_QSTR(MP_QSTR_GPIO16), MP_ROM_PTR(&pin_GPIO16) }, - { MP_ROM_QSTR(MP_QSTR_GPIO17), MP_ROM_PTR(&pin_GPIO17) }, - { MP_ROM_QSTR(MP_QSTR_GPIO18), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_GPIO19), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_GPIO20), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_GPIO21), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_GPIO22), MP_ROM_PTR(&pin_GPIO22) }, - { MP_ROM_QSTR(MP_QSTR_GPIO23), MP_ROM_PTR(&pin_GPIO23) }, - { MP_ROM_QSTR(MP_QSTR_GPIO24), MP_ROM_PTR(&pin_GPIO24) }, - { MP_ROM_QSTR(MP_QSTR_GPIO25), MP_ROM_PTR(&pin_GPIO25) }, - { MP_ROM_QSTR(MP_QSTR_GPIO26), MP_ROM_PTR(&pin_GPIO26) }, - { MP_ROM_QSTR(MP_QSTR_GPIO27), MP_ROM_PTR(&pin_GPIO27) }, - - { MP_ROM_QSTR(MP_QSTR_LED_nACT), MP_ROM_PTR(&pin_GPIO42) }, - { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO42) }, - - { MP_ROM_QSTR(MP_QSTR_SDA0), MP_ROM_PTR(&pin_GPIO44) }, - { MP_ROM_QSTR(MP_QSTR_SCL0), MP_ROM_PTR(&pin_GPIO45) }, - - { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display)}, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/broadcom/boards/raspberrypi_cm4io/board.c b/ports/broadcom/boards/raspberrypi_cm4io/board.c deleted file mode 100644 index 612d93776df..00000000000 --- a/ports/broadcom/boards/raspberrypi_cm4io/board.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/board.h" -#include "mpconfigboard.h" - -#include "bindings/videocore/Framebuffer.h" -#include "shared-module/displayio/__init__.h" -#include "shared-bindings/framebufferio/FramebufferDisplay.h" - -void board_init(void) { - videocore_framebuffer_obj_t *fb = &allocate_display_bus()->videocore; - fb->base.type = &videocore_framebuffer_type; - common_hal_videocore_framebuffer_construct(fb, 640, 480); - - framebufferio_framebufferdisplay_obj_t *display = &allocate_display()->framebuffer_display; - display->base.type = &framebufferio_framebufferdisplay_type; - common_hal_framebufferio_framebufferdisplay_construct( - display, - MP_OBJ_FROM_PTR(fb), - 0, - true); -} - -// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/broadcom/boards/raspberrypi_cm4io/mpconfigboard.h b/ports/broadcom/boards/raspberrypi_cm4io/mpconfigboard.h deleted file mode 100644 index 2c02fabd9ec..00000000000 --- a/ports/broadcom/boards/raspberrypi_cm4io/mpconfigboard.h +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_HW_BOARD_NAME "Raspberry Pi Compute Module 4 IO Board" - -#define DEFAULT_I2C_BUS_SCL (&pin_GPIO3) -#define DEFAULT_I2C_BUS_SDA (&pin_GPIO2) - -#define MICROPY_HW_LED_STATUS (&pin_GPIO42) - -#define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO14) -#define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO15) diff --git a/ports/broadcom/boards/raspberrypi_cm4io/mpconfigboard.mk b/ports/broadcom/boards/raspberrypi_cm4io/mpconfigboard.mk deleted file mode 100644 index d021462bc6b..00000000000 --- a/ports/broadcom/boards/raspberrypi_cm4io/mpconfigboard.mk +++ /dev/null @@ -1,6 +0,0 @@ -USB_VID = 0x2E8A -USB_PID = 0x1012 -USB_PRODUCT = "Compute Module 4 IO Board" -USB_MANUFACTURER = "Raspberry Pi" - -CHIP_VARIANT = "bcm2711" diff --git a/ports/broadcom/boards/raspberrypi_cm4io/pins.c b/ports/broadcom/boards/raspberrypi_cm4io/pins.c deleted file mode 100644 index d9666eafda3..00000000000 --- a/ports/broadcom/boards/raspberrypi_cm4io/pins.c +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -#include "shared-module/displayio/__init__.h" - -static const mp_rom_map_elem_t board_global_dict_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - - // These match the names used in Blinka - { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO1) }, - - { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO3) }, - { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO3) }, - - { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO6) }, - - { MP_ROM_QSTR(MP_QSTR_D7), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_CE1), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_D8), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_CE0), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCLK), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO11) }, - - { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO12) }, - { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO13) }, - - { MP_ROM_QSTR(MP_QSTR_D14), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_TXD), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_RXD), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO15) }, - - { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO16) }, - { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO17) }, - { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_MISO_1), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_D20), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_MOSI_1), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_D21), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCLK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_D22), MP_ROM_PTR(&pin_GPIO22) }, - { MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO23) }, - { MP_ROM_QSTR(MP_QSTR_D24), MP_ROM_PTR(&pin_GPIO24) }, - { MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO25) }, - { MP_ROM_QSTR(MP_QSTR_D26), MP_ROM_PTR(&pin_GPIO26) }, - { MP_ROM_QSTR(MP_QSTR_D27), MP_ROM_PTR(&pin_GPIO27) }, - - { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO42) }, - - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, - { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display)}, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/broadcom/boards/raspberrypi_pi4b/board.c b/ports/broadcom/boards/raspberrypi_pi4b/board.c deleted file mode 100644 index 612d93776df..00000000000 --- a/ports/broadcom/boards/raspberrypi_pi4b/board.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/board.h" -#include "mpconfigboard.h" - -#include "bindings/videocore/Framebuffer.h" -#include "shared-module/displayio/__init__.h" -#include "shared-bindings/framebufferio/FramebufferDisplay.h" - -void board_init(void) { - videocore_framebuffer_obj_t *fb = &allocate_display_bus()->videocore; - fb->base.type = &videocore_framebuffer_type; - common_hal_videocore_framebuffer_construct(fb, 640, 480); - - framebufferio_framebufferdisplay_obj_t *display = &allocate_display()->framebuffer_display; - display->base.type = &framebufferio_framebufferdisplay_type; - common_hal_framebufferio_framebufferdisplay_construct( - display, - MP_OBJ_FROM_PTR(fb), - 0, - true); -} - -// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/broadcom/boards/raspberrypi_pi4b/mpconfigboard.h b/ports/broadcom/boards/raspberrypi_pi4b/mpconfigboard.h deleted file mode 100644 index 45eeff3b59b..00000000000 --- a/ports/broadcom/boards/raspberrypi_pi4b/mpconfigboard.h +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_HW_BOARD_NAME "Raspberry Pi 4B" - -#define DEFAULT_I2C_BUS_SCL (&pin_GPIO3) -#define DEFAULT_I2C_BUS_SDA (&pin_GPIO2) - -#define MICROPY_HW_LED_STATUS (&pin_GPIO42) - -#define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO14) -#define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO15) diff --git a/ports/broadcom/boards/raspberrypi_pi4b/mpconfigboard.mk b/ports/broadcom/boards/raspberrypi_pi4b/mpconfigboard.mk deleted file mode 100644 index 2c808931da5..00000000000 --- a/ports/broadcom/boards/raspberrypi_pi4b/mpconfigboard.mk +++ /dev/null @@ -1,6 +0,0 @@ -USB_VID = 0x2E8A -USB_PID = 0x1013 -USB_PRODUCT = "Raspberry Pi 4B" -USB_MANUFACTURER = "Raspberry Pi" - -CHIP_VARIANT = "bcm2711" diff --git a/ports/broadcom/boards/raspberrypi_pi4b/pins.c b/ports/broadcom/boards/raspberrypi_pi4b/pins.c deleted file mode 100644 index d9666eafda3..00000000000 --- a/ports/broadcom/boards/raspberrypi_pi4b/pins.c +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -#include "shared-module/displayio/__init__.h" - -static const mp_rom_map_elem_t board_global_dict_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - - // These match the names used in Blinka - { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO1) }, - - { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO3) }, - { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO3) }, - - { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO6) }, - - { MP_ROM_QSTR(MP_QSTR_D7), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_CE1), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_D8), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_CE0), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCLK), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO11) }, - - { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO12) }, - { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO13) }, - - { MP_ROM_QSTR(MP_QSTR_D14), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_TXD), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_RXD), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO15) }, - - { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO16) }, - { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO17) }, - { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_MISO_1), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_D20), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_MOSI_1), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_D21), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCLK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_D22), MP_ROM_PTR(&pin_GPIO22) }, - { MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO23) }, - { MP_ROM_QSTR(MP_QSTR_D24), MP_ROM_PTR(&pin_GPIO24) }, - { MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO25) }, - { MP_ROM_QSTR(MP_QSTR_D26), MP_ROM_PTR(&pin_GPIO26) }, - { MP_ROM_QSTR(MP_QSTR_D27), MP_ROM_PTR(&pin_GPIO27) }, - - { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO42) }, - - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, - { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display)}, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/broadcom/boards/raspberrypi_zero/board.c b/ports/broadcom/boards/raspberrypi_zero/board.c deleted file mode 100644 index 612d93776df..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero/board.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/board.h" -#include "mpconfigboard.h" - -#include "bindings/videocore/Framebuffer.h" -#include "shared-module/displayio/__init__.h" -#include "shared-bindings/framebufferio/FramebufferDisplay.h" - -void board_init(void) { - videocore_framebuffer_obj_t *fb = &allocate_display_bus()->videocore; - fb->base.type = &videocore_framebuffer_type; - common_hal_videocore_framebuffer_construct(fb, 640, 480); - - framebufferio_framebufferdisplay_obj_t *display = &allocate_display()->framebuffer_display; - display->base.type = &framebufferio_framebufferdisplay_type; - common_hal_framebufferio_framebufferdisplay_construct( - display, - MP_OBJ_FROM_PTR(fb), - 0, - true); -} - -// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/broadcom/boards/raspberrypi_zero/mpconfigboard.h b/ports/broadcom/boards/raspberrypi_zero/mpconfigboard.h deleted file mode 100644 index 64da629317e..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero/mpconfigboard.h +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_HW_BOARD_NAME "Raspberry Pi Zero" - -#define DEFAULT_I2C_BUS_SCL (&pin_GPIO3) -#define DEFAULT_I2C_BUS_SDA (&pin_GPIO2) - -#define MICROPY_HW_LED_STATUS (&pin_GPIO47) - -#define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO14) -#define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO15) diff --git a/ports/broadcom/boards/raspberrypi_zero/mpconfigboard.mk b/ports/broadcom/boards/raspberrypi_zero/mpconfigboard.mk deleted file mode 100644 index b6adb5d6bb8..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero/mpconfigboard.mk +++ /dev/null @@ -1,8 +0,0 @@ -USB_VID = 0x2E8A -USB_PID = 0x100E -USB_PRODUCT = "Zero" -USB_MANUFACTURER = "Raspberry Pi" - -CHIP_VARIANT = "bcm2835" - -CIRCUITPY_BUILD_EXTENSIONS = disk.img.zip,kernel.img diff --git a/ports/broadcom/boards/raspberrypi_zero/pins.c b/ports/broadcom/boards/raspberrypi_zero/pins.c deleted file mode 100644 index 4b3cf228135..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero/pins.c +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -#include "shared-module/displayio/__init__.h" - -static const mp_rom_map_elem_t board_global_dict_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - // These match the names used in Blinka - { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO1) }, - - { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO3) }, - { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO3) }, - - { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO6) }, - - { MP_ROM_QSTR(MP_QSTR_D7), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_CE1), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_D8), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_CE0), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCLK), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO11) }, - - { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO12) }, - { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO13) }, - - { MP_ROM_QSTR(MP_QSTR_D14), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_TXD), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_RXD), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO15) }, - - { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO16) }, - { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO17) }, - { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_MISO_1), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_D20), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_MOSI_1), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_D21), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCLK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_D22), MP_ROM_PTR(&pin_GPIO22) }, - { MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO23) }, - { MP_ROM_QSTR(MP_QSTR_D24), MP_ROM_PTR(&pin_GPIO24) }, - { MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO25) }, - { MP_ROM_QSTR(MP_QSTR_D26), MP_ROM_PTR(&pin_GPIO26) }, - { MP_ROM_QSTR(MP_QSTR_D27), MP_ROM_PTR(&pin_GPIO27) }, - - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, - { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display)}, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/broadcom/boards/raspberrypi_zero2w/board.c b/ports/broadcom/boards/raspberrypi_zero2w/board.c deleted file mode 100644 index 612d93776df..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero2w/board.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/board.h" -#include "mpconfigboard.h" - -#include "bindings/videocore/Framebuffer.h" -#include "shared-module/displayio/__init__.h" -#include "shared-bindings/framebufferio/FramebufferDisplay.h" - -void board_init(void) { - videocore_framebuffer_obj_t *fb = &allocate_display_bus()->videocore; - fb->base.type = &videocore_framebuffer_type; - common_hal_videocore_framebuffer_construct(fb, 640, 480); - - framebufferio_framebufferdisplay_obj_t *display = &allocate_display()->framebuffer_display; - display->base.type = &framebufferio_framebufferdisplay_type; - common_hal_framebufferio_framebufferdisplay_construct( - display, - MP_OBJ_FROM_PTR(fb), - 0, - true); -} - -// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/broadcom/boards/raspberrypi_zero2w/mpconfigboard.h b/ports/broadcom/boards/raspberrypi_zero2w/mpconfigboard.h deleted file mode 100644 index 4168832fb82..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero2w/mpconfigboard.h +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_HW_BOARD_NAME "Raspberry Pi Zero 2W" - -#define DEFAULT_I2C_BUS_SCL (&pin_GPIO3) -#define DEFAULT_I2C_BUS_SDA (&pin_GPIO2) - -#define MICROPY_HW_LED_STATUS (&pin_GPIO29) - -#define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO14) -#define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO15) diff --git a/ports/broadcom/boards/raspberrypi_zero2w/mpconfigboard.mk b/ports/broadcom/boards/raspberrypi_zero2w/mpconfigboard.mk deleted file mode 100644 index 1479cb3b8c4..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero2w/mpconfigboard.mk +++ /dev/null @@ -1,6 +0,0 @@ -USB_VID = 0x2E8A -USB_PID = 0x1015 -USB_PRODUCT = "Raspberry Pi Zero 2W" -USB_MANUFACTURER = "Raspberry Pi" - -CHIP_VARIANT = "bcm2837" diff --git a/ports/broadcom/boards/raspberrypi_zero2w/pins.c b/ports/broadcom/boards/raspberrypi_zero2w/pins.c deleted file mode 100644 index 1d8f247487e..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero2w/pins.c +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -#include "shared-module/displayio/__init__.h" - -static const mp_rom_map_elem_t board_global_dict_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - // These match the names used in Blinka - { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO1) }, - - { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO3) }, - { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO3) }, - - { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO6) }, - - { MP_ROM_QSTR(MP_QSTR_D7), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_CE1), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_D8), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_CE0), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCLK), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO11) }, - - { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO12) }, - { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO13) }, - - { MP_ROM_QSTR(MP_QSTR_D14), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_TXD), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_RXD), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO15) }, - - { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO16) }, - { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO17) }, - { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_MISO_1), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_D20), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_MOSI_1), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_D21), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCLK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_D22), MP_ROM_PTR(&pin_GPIO22) }, - { MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO23) }, - { MP_ROM_QSTR(MP_QSTR_D24), MP_ROM_PTR(&pin_GPIO24) }, - { MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO25) }, - { MP_ROM_QSTR(MP_QSTR_D26), MP_ROM_PTR(&pin_GPIO26) }, - { MP_ROM_QSTR(MP_QSTR_D27), MP_ROM_PTR(&pin_GPIO27) }, - { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO29) }, - - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, - { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display)}, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/broadcom/boards/raspberrypi_zero_w/board.c b/ports/broadcom/boards/raspberrypi_zero_w/board.c deleted file mode 100644 index 612d93776df..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero_w/board.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/board.h" -#include "mpconfigboard.h" - -#include "bindings/videocore/Framebuffer.h" -#include "shared-module/displayio/__init__.h" -#include "shared-bindings/framebufferio/FramebufferDisplay.h" - -void board_init(void) { - videocore_framebuffer_obj_t *fb = &allocate_display_bus()->videocore; - fb->base.type = &videocore_framebuffer_type; - common_hal_videocore_framebuffer_construct(fb, 640, 480); - - framebufferio_framebufferdisplay_obj_t *display = &allocate_display()->framebuffer_display; - display->base.type = &framebufferio_framebufferdisplay_type; - common_hal_framebufferio_framebufferdisplay_construct( - display, - MP_OBJ_FROM_PTR(fb), - 0, - true); -} - -// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/broadcom/boards/raspberrypi_zero_w/mpconfigboard.h b/ports/broadcom/boards/raspberrypi_zero_w/mpconfigboard.h deleted file mode 100644 index 526a152011e..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero_w/mpconfigboard.h +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_HW_BOARD_NAME "Raspberry Pi Zero W" - -#define DEFAULT_I2C_BUS_SCL (&pin_GPIO3) -#define DEFAULT_I2C_BUS_SDA (&pin_GPIO2) - -#define MICROPY_HW_LED_STATUS (&pin_GPIO47) - -#define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO14) -#define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO15) diff --git a/ports/broadcom/boards/raspberrypi_zero_w/mpconfigboard.mk b/ports/broadcom/boards/raspberrypi_zero_w/mpconfigboard.mk deleted file mode 100644 index cd55f73ee84..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero_w/mpconfigboard.mk +++ /dev/null @@ -1,8 +0,0 @@ -USB_VID = 0x2E8A -USB_PID = 0x101E -USB_PRODUCT = "Zero W" -USB_MANUFACTURER = "Raspberry Pi" - -CHIP_VARIANT = "bcm2835" - -CIRCUITPY_BUILD_EXTENSIONS = disk.img.zip,kernel.img diff --git a/ports/broadcom/boards/raspberrypi_zero_w/pins.c b/ports/broadcom/boards/raspberrypi_zero_w/pins.c deleted file mode 100644 index 4b3cf228135..00000000000 --- a/ports/broadcom/boards/raspberrypi_zero_w/pins.c +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -#include "shared-module/displayio/__init__.h" - -static const mp_rom_map_elem_t board_global_dict_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - // These match the names used in Blinka - { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO1) }, - - { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO3) }, - { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO3) }, - - { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO6) }, - - { MP_ROM_QSTR(MP_QSTR_D7), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_CE1), MP_ROM_PTR(&pin_GPIO7) }, - { MP_ROM_QSTR(MP_QSTR_D8), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_CE0), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCLK), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO11) }, - - { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO12) }, - { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO13) }, - - { MP_ROM_QSTR(MP_QSTR_D14), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_TXD), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_RXD), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO15) }, - - { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO16) }, - { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO17) }, - { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_MISO_1), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_D20), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_MOSI_1), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_D21), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCLK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_SCK_1), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_D22), MP_ROM_PTR(&pin_GPIO22) }, - { MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO23) }, - { MP_ROM_QSTR(MP_QSTR_D24), MP_ROM_PTR(&pin_GPIO24) }, - { MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO25) }, - { MP_ROM_QSTR(MP_QSTR_D26), MP_ROM_PTR(&pin_GPIO26) }, - { MP_ROM_QSTR(MP_QSTR_D27), MP_ROM_PTR(&pin_GPIO27) }, - - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, - { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display)}, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/broadcom/broadcom_peripherals_config.h b/ports/broadcom/broadcom_peripherals_config.h deleted file mode 100644 index 64d35affea3..00000000000 --- a/ports/broadcom/broadcom_peripherals_config.h +++ /dev/null @@ -1,14 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/obj.h" - -extern const mp_obj_type_t mcu_pin_type; - -#define PIN_PREFIX_VALUES { &mcu_pin_type }, -#define PIN_PREFIX_FIELDS mp_obj_base_t base; diff --git a/ports/broadcom/common-hal/board/__init__.c b/ports/broadcom/common-hal/board/__init__.c deleted file mode 100644 index bbb34aa3454..00000000000 --- a/ports/broadcom/common-hal/board/__init__.c +++ /dev/null @@ -1,14 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include - -#include "py/runtime.h" -#include "py/mphal.h" -#include "common-hal/microcontroller/Pin.h" - -// Pins aren't actually defined here. They are in the board specific directory -// such as boards/arduino_zero/pins.c. diff --git a/ports/broadcom/common-hal/busio/I2C.c b/ports/broadcom/common-hal/busio/I2C.c deleted file mode 100644 index 7c1eafe281d..00000000000 --- a/ports/broadcom/common-hal/busio/I2C.c +++ /dev/null @@ -1,260 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/mperrno.h" -#include "py/mphal.h" -#include "shared-bindings/busio/I2C.h" -#include "py/runtime.h" - -#include "shared-bindings/microcontroller/__init__.h" -#include "shared-bindings/microcontroller/Pin.h" - -#include "peripherals/broadcom/cpu.h" -#include "peripherals/broadcom/vcmailbox.h" - -#if BCM_VERSION == 2711 -#define NUM_I2C (8) -static BSC0_Type *i2c[NUM_I2C] = {BSC0, BSC1, NULL, BSC3, BSC4, BSC5, BSC6, NULL}; -#else -#define NUM_I2C (3) -static BSC0_Type *i2c[NUM_I2C] = {BSC0, BSC1, NULL}; -#endif - -static bool never_reset_i2c[NUM_I2C]; -static bool i2c_in_use[NUM_I2C]; - -void reset_i2c(void) { - // BSC2 is dedicated to the first HDMI output. - never_reset_i2c[2] = true; - i2c_in_use[2] = true; - #if BCM_VERSION == 2711 - // BSC7 is dedicated to the second HDMI output. - never_reset_i2c[7] = true; - i2c_in_use[7] = true; - #endif - for (size_t i = 0; i < NUM_I2C; i++) { - if (never_reset_i2c[i]) { - continue; - } - i2c_in_use[i] = false; - i2c[i]->C_b.I2CEN = false; - COMPLETE_MEMORY_READS; - } -} - -void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, - const mcu_pin_obj_t *scl, const mcu_pin_obj_t *sda, uint32_t frequency, uint32_t timeout) { - - // Ensure the object starts in its deinit state. - common_hal_busio_i2c_mark_deinit(self); - - size_t instance_index = NUM_I2C; - uint8_t scl_alt = 0; - uint8_t sda_alt = 0; - - for (scl_alt = 0; scl_alt < 6; scl_alt++) { - if (scl->functions[scl_alt].type != PIN_FUNCTION_I2C || - i2c_in_use[scl->functions[scl_alt].index] || - scl->functions[scl_alt].function != I2C_FUNCTION_SCL) { - continue; - } - for (sda_alt = 0; sda_alt < 6; sda_alt++) { - if (sda->functions[sda_alt].type != PIN_FUNCTION_I2C || - scl->functions[scl_alt].index != sda->functions[sda_alt].index || - sda->functions[sda_alt].function != I2C_FUNCTION_SDA) { - continue; - } - instance_index = scl->functions[scl_alt].index; - break; - } - if (instance_index != NUM_I2C) { - break; - } - } - if (instance_index == NUM_I2C) { - raise_ValueError_invalid_pins(); - } - i2c_in_use[instance_index] = true; - self->index = instance_index; - self->peripheral = i2c[self->index]; - self->sda_pin = sda; - self->scl_pin = scl; - - uint32_t source_clock = vcmailbox_get_clock_rate(VCMAILBOX_CLOCK_CORE); - uint16_t clock_divider = source_clock / frequency; - self->peripheral->DIV_b.CDIV = clock_divider; - - gpio_set_function(sda->number, FSEL_VALUES[sda_alt]); - gpio_set_function(scl->number, FSEL_VALUES[scl_alt]); -} - -bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { - return self->sda_pin == NULL; -} - -void common_hal_busio_i2c_mark_deinit(busio_i2c_obj_t *self) { - self->sda_pin = NULL; -} - -void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) { - if (common_hal_busio_i2c_deinited(self)) { - return; - } - i2c_in_use[self->index] = false; - - common_hal_reset_pin(self->sda_pin); - common_hal_reset_pin(self->scl_pin); - common_hal_busio_i2c_mark_deinit(self); -} - -bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) { - uint8_t result = common_hal_busio_i2c_write(self, addr, NULL, 0); - return result == 0; -} - -bool common_hal_busio_i2c_try_lock(busio_i2c_obj_t *self) { - if (common_hal_busio_i2c_deinited(self)) { - return false; - } - bool grabbed_lock = false; - if (!self->has_lock) { - grabbed_lock = true; - self->has_lock = true; - } - return grabbed_lock; -} - -bool common_hal_busio_i2c_has_lock(busio_i2c_obj_t *self) { - return self->has_lock; -} - -void common_hal_busio_i2c_unlock(busio_i2c_obj_t *self) { - self->has_lock = false; -} - -// Discussion of I2C implementation is here: https://github.com/raspberrypi/linux/issues/254 - -static mp_negative_errno_t _common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr, - const uint8_t *data, size_t len, bool transmit_stop_bit) { - COMPLETE_MEMORY_READS; - self->peripheral->S_b.DONE = true; - self->peripheral->A_b.ADDR = addr; - size_t loop_len = len; - // Prevent the stop bit by transmitting everything but the last byte. Doing - // so is left up to the subsequent read. - if (!transmit_stop_bit) { - loop_len -= 1; - } - self->peripheral->DLEN_b.DLEN = len; - self->peripheral->C = BSC0_C_ST_Msk | BSC0_C_I2CEN_Msk; - // Wait for the transaction to start. - while (self->peripheral->S_b.TA == 0) { - RUN_BACKGROUND_TASKS; - } - for (size_t i = 0; i < loop_len; i++) { - if (self->peripheral->S_b.ERR) { - break; - } - self->peripheral->FIFO_b.DATA = data[i]; - // Wait for the FIFO to empty enough that we can write more data. - while (self->peripheral->S_b.TXE == 0) { - RUN_BACKGROUND_TASKS; - } - } - // Wait for the FIFO to empty completely, not DONE, because we may not complete the - // transaction with a write. - while (self->peripheral->S_b.ERR == 0 && - ((!transmit_stop_bit && self->peripheral->S_b.TXE == 0) || - (transmit_stop_bit && self->peripheral->S_b.TA == 1))) { - RUN_BACKGROUND_TASKS; - } - self->finish_write = false; - if (self->peripheral->S_b.ERR) { - // Wait for the transfer to finish. - while (self->peripheral->S_b.TA == 1) { - RUN_BACKGROUND_TASKS; - } - // Clear the flag by writing it and wait for it to clear. - self->peripheral->S_b.ERR = true; - while (self->peripheral->S_b.ERR == 1) { - RUN_BACKGROUND_TASKS; - } - return -MP_ENODEV; - } - - if (loop_len < len) { - self->finish_write = true; - self->last_write_data = data[len - 1]; - } - return 0; -} - -mp_negative_errno_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr, - const uint8_t *data, size_t len) { - return _common_hal_busio_i2c_write(self, addr, data, len, true); -} - -mp_negative_errno_t common_hal_busio_i2c_read(busio_i2c_obj_t *self, uint16_t addr, - uint8_t *data, size_t len) { - COMPLETE_MEMORY_READS; - self->peripheral->A_b.ADDR = addr; - if (self->finish_write) { - self->finish_write = false; - if (self->peripheral->S_b.ERR == 1) { - return -MP_ENODEV; - } - self->peripheral->FIFO_b.DATA = self->last_write_data; - } else { - self->peripheral->S_b.DONE = true; - } - self->peripheral->DLEN_b.DLEN = len; - self->peripheral->C = BSC0_C_READ_Msk | BSC0_C_ST_Msk | BSC0_C_I2CEN_Msk; - // Wait for the transaction to start. - while (self->peripheral->S_b.TA == 0) { - RUN_BACKGROUND_TASKS; - } - for (size_t i = 0; i < len; i++) { - if (self->peripheral->S_b.ERR) { - break; - } - // Wait for the FIFO to have enough data that we can read it. RXR is low - // once the transaction is done so we check the done bit too. - while (!self->peripheral->S_b.RXR && !self->peripheral->S_b.DONE) { - RUN_BACKGROUND_TASKS; - } - data[i] = self->peripheral->FIFO_b.DATA; - } - // Wait for the transaction to finish. - while (!self->peripheral->S_b.DONE && !self->peripheral->S_b.ERR) { - RUN_BACKGROUND_TASKS; - } - if (self->peripheral->S_b.ERR) { - self->peripheral->S_b.ERR = true; - while (self->peripheral->S_b.ERR == 1) { - RUN_BACKGROUND_TASKS; - } - return -MP_ENODEV; - } - - return 0; -} - -mp_negative_errno_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint16_t addr, - uint8_t *out_data, size_t out_len, uint8_t *in_data, size_t in_len) { - mp_negative_errno_t result = _common_hal_busio_i2c_write(self, addr, out_data, out_len, false); - if (result != 0) { - return result; - } - - return common_hal_busio_i2c_read(self, addr, in_data, in_len); -} - -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - never_reset_i2c[self->index] = true; - - common_hal_never_reset_pin(self->scl_pin); - common_hal_never_reset_pin(self->sda_pin); -} diff --git a/ports/broadcom/common-hal/busio/I2C.h b/ports/broadcom/common-hal/busio/I2C.h deleted file mode 100644 index 11416c0203f..00000000000 --- a/ports/broadcom/common-hal/busio/I2C.h +++ /dev/null @@ -1,27 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "common-hal/microcontroller/Pin.h" - -#include "py/obj.h" - -#include "peripherals/broadcom/defines.h" -#include "peripherals/broadcom/gpio.h" - -typedef struct { - mp_obj_base_t base; - const mcu_pin_obj_t *scl_pin; - const mcu_pin_obj_t *sda_pin; - BSC0_Type *peripheral; - uint8_t index; - bool has_lock; - bool finish_write; - uint8_t last_write_data; -} busio_i2c_obj_t; - -void reset_i2c(void); diff --git a/ports/broadcom/common-hal/busio/SPI.c b/ports/broadcom/common-hal/busio/SPI.c deleted file mode 100644 index 9e4834e86f3..00000000000 --- a/ports/broadcom/common-hal/busio/SPI.c +++ /dev/null @@ -1,317 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/busio/SPI.h" - -#include "shared/runtime/interrupt_char.h" -#include "py/mperrno.h" -#include "py/runtime.h" - -#include "supervisor/board.h" -#include "common-hal/microcontroller/Pin.h" -#include "shared-bindings/microcontroller/Pin.h" - -#include "peripherals/broadcom/cpu.h" -#include "peripherals/broadcom/defines.h" -#include "peripherals/broadcom/gpio.h" -#include "peripherals/broadcom/pins.h" -#include "peripherals/broadcom/vcmailbox.h" - -#if BCM_VERSION == 2711 -#define NUM_SPI (7) -static SPI0_Type *spi[NUM_SPI] = {SPI0, NULL, NULL, SPI3, SPI4, SPI5, SPI6}; -static SPI1_Type *aux_spi[NUM_SPI] = {NULL, SPI1, SPI2, NULL, NULL, NULL, NULL}; -#else -#define NUM_SPI (3) -static SPI0_Type *spi[NUM_SPI] = {SPI0, NULL, NULL}; -static SPI1_Type *aux_spi[NUM_SPI] = {NULL, SPI1, SPI2}; -#endif - -static bool spi_in_use[NUM_SPI]; - -void common_hal_busio_spi_construct(busio_spi_obj_t *self, - const mcu_pin_obj_t *clock, const mcu_pin_obj_t *mosi, - const mcu_pin_obj_t *miso, bool half_duplex) { - size_t instance_index = NUM_SPI; - BP_Function_Enum clock_alt = 0; - BP_Function_Enum mosi_alt = 0; - BP_Function_Enum miso_alt = 0; - - if (half_duplex) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("Half duplex SPI is not implemented")); - } - - // Ensure the object starts in its deinit state. - common_hal_busio_spi_mark_deinit(self); - - // BCM_VERSION != 2711 have 3 SPI but as listed in peripherals/gen/pins.c two are on - // index 0, once one index 0 SPI is found the other will throw an invalid_pins error. - for (size_t i = 0; i < NUM_SPI; i++) { - if (spi_in_use[i]) { - continue; - } - if (!pin_find_alt(clock, PIN_FUNCTION_SPI, i, SPI_FUNCTION_SCLK, &clock_alt)) { - continue; - } - if (mosi != NULL && !pin_find_alt(mosi, PIN_FUNCTION_SPI, i, SPI_FUNCTION_MOSI, &mosi_alt)) { - continue; - } - if (miso != NULL && !pin_find_alt(miso, PIN_FUNCTION_SPI, i, SPI_FUNCTION_MISO, &miso_alt)) { - continue; - } - instance_index = i; - break; - } - if (instance_index == NUM_SPI) { - raise_ValueError_invalid_pins(); - } - - self->clock = clock; - self->MOSI = mosi; - self->MISO = miso; - self->index = instance_index; - spi_in_use[instance_index] = true; - - - if (instance_index == 1) { - AUX->ENABLES_b.SPI_1 = true; - } else if (instance_index == 2) { - AUX->ENABLES_b.SPI_2 = true; - } - - common_hal_busio_spi_configure(self, 250000, 0, 0, 8); - - COMPLETE_MEMORY_READS; - gpio_set_pull(clock->number, BP_PULL_NONE); - gpio_set_function(clock->number, clock_alt); - if (mosi != NULL) { - gpio_set_pull(mosi->number, BP_PULL_NONE); - gpio_set_function(mosi->number, mosi_alt); - } - if (miso != NULL) { - gpio_set_pull(miso->number, BP_PULL_NONE); - gpio_set_function(miso->number, miso_alt); - } -} - -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { - common_hal_never_reset_pin(self->clock); - common_hal_never_reset_pin(self->MOSI); - common_hal_never_reset_pin(self->MISO); -} - -bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { - return self->clock == NULL; -} - -void common_hal_busio_spi_mark_deinit(busio_spi_obj_t *self) { - self->clock = NULL; -} - -void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { - if (common_hal_busio_spi_deinited(self)) { - return; - } - - common_hal_reset_pin(self->clock); - common_hal_reset_pin(self->MOSI); - common_hal_reset_pin(self->MISO); - - spi_in_use[self->index] = false; - - if (self->index == 1 || - self->index == 2) { - aux_spi[self->index]->CNTL0_b.ENABLE = false; - if (self->index == 1) { - AUX->ENABLES_b.SPI_1 = false; - } else if (self->index == 2) { - AUX->ENABLES_b.SPI_2 = false; - } - } else { - // Set CS back to default. All 0 except read enable. - spi[self->index]->CS = SPI0_CS_REN_Msk; - } - - common_hal_busio_spi_mark_deinit(self); -} - -bool common_hal_busio_spi_configure(busio_spi_obj_t *self, - uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) { - if (baudrate == self->target_frequency && - polarity == self->polarity && - phase == self->phase && - bits == self->bits) { - return true; - } - - if (self->index == 1 || self->index == 2) { - SPI1_Type *p = aux_spi[self->index]; - uint32_t source_clock = vcmailbox_get_clock_rate(VCMAILBOX_CLOCK_CORE); - uint16_t clock_divider = source_clock / baudrate; - if (source_clock % baudrate > 0) { - clock_divider += 2; - } - - p->CNTL0 = (clock_divider / 2 - 1) << SPI1_CNTL0_SPEED_Pos | - SPI1_CNTL0_ENABLE_Msk | - SPI1_CNTL0_MSB_FIRST_Msk | - (polarity == 1? SPI1_CNTL0_INVERT_CLK_Msk : 0) | - (phase == polarity? SPI1_CNTL0_IN_RISING_Msk : SPI1_CNTL0_OUT_RISING_Msk) | - 8 << SPI1_CNTL0_SHIFT_LENGTH_Pos; - p->CNTL1 = SPI1_CNTL1_MSB_FIRST_Msk; - self->real_frequency = source_clock / clock_divider; - } else { - SPI0_Type *p = spi[self->index]; - p->CS = polarity << SPI0_CS_CPOL_Pos | - phase << SPI0_CS_CPHA_Pos; - uint32_t source_clock = vcmailbox_get_clock_rate(VCMAILBOX_CLOCK_CORE); - uint16_t clock_divider = source_clock / baudrate; - if (source_clock % baudrate > 0) { - clock_divider += 2; - } - - p->CLK = clock_divider; - self->real_frequency = source_clock / clock_divider; - } - - self->polarity = polarity; - self->phase = phase; - self->bits = bits; - self->target_frequency = baudrate; - - return true; -} - -bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) { - if (common_hal_busio_spi_deinited(self)) { - return false; - } - bool grabbed_lock = false; - if (!self->has_lock) { - grabbed_lock = true; - self->has_lock = true; - } - return grabbed_lock; -} - -bool common_hal_busio_spi_has_lock(busio_spi_obj_t *self) { - return self->has_lock; -} - -void common_hal_busio_spi_unlock(busio_spi_obj_t *self) { - self->has_lock = false; -} - -static void _spi_transfer(SPI0_Type *p, - const uint8_t *data_out, size_t out_len, - uint8_t *data_in, size_t in_len) { - size_t len = MAX(out_len, in_len); - COMPLETE_MEMORY_READS; - p->DLEN = len; - p->CS |= SPI0_CS_TA_Msk | SPI0_CS_CLEAR_Msk; - size_t in = 0; - size_t out = 0; - while (in < len) { - while (out < len && p->CS_b.TXD == 1) { - if (out_len == 1) { - p->FIFO = data_out[0]; - } else { - p->FIFO = data_out[out]; - } - out++; - } - // Wait for data to read (also means data has been sent.) - while (p->CS_b.RXD == 0) { - RUN_BACKGROUND_TASKS; - } - while (p->CS_b.RXD == 1) { - uint8_t data = p->FIFO; - if (data_in != NULL) { - data_in[in] = data; - } else { - (void)data; - } - in++; - } - } - p->CS_b.TA = false; - COMPLETE_MEMORY_READS; -} - -static void _aux_spi_transfer(SPI1_Type *p, - const uint8_t *data_out, size_t out_len, - uint8_t *data_in, size_t in_len) { - size_t len = MAX(out_len, in_len); - p->CNTL0 |= SPI1_CNTL0_CLEAR_FIFOS_Msk; - p->CNTL0 &= ~SPI1_CNTL0_CLEAR_FIFOS_Msk; - size_t in = 0; - size_t out = 0; - while (in < len) { - while (out < len && p->STAT_b.TX_FULL == 0) { - if (out_len == 1) { - p->TXHOLD0 = data_out[0] << 24; - } else { - p->TXHOLD0 = data_out[out] << 24; - } - out++; - } - // Wait for data to read (also means data has been sent.) - while (p->STAT_b.RX_EMPTY == 1) { - RUN_BACKGROUND_TASKS; - } - while (p->STAT_b.RX_EMPTY == 0) { - uint8_t data = p->TXHOLD0; - if (data_in != NULL) { - data_in[in] = data; - } else { - (void)data; - } - in++; - } - } -} - -bool common_hal_busio_spi_write(busio_spi_obj_t *self, - const uint8_t *data, size_t len) { - if (self->index == 1 || self->index == 2) { - _aux_spi_transfer(aux_spi[self->index], data, len, NULL, 0); - } else { - _spi_transfer(spi[self->index], data, len, NULL, 0); - } - return true; -} - -bool common_hal_busio_spi_read(busio_spi_obj_t *self, - uint8_t *data, size_t len, uint8_t write_value) { - if (self->index == 1 || self->index == 2) { - _aux_spi_transfer(aux_spi[self->index], &write_value, 1, data, len); - } else { - _spi_transfer(spi[self->index], &write_value, 1, data, len); - } - return true; -} - -bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, - const uint8_t *data_out, uint8_t *data_in, size_t len) { - if (self->index == 1 || self->index == 2) { - _aux_spi_transfer(aux_spi[self->index], data_out, len, data_in, len); - } else { - _spi_transfer(spi[self->index], data_out, len, data_in, len); - } - return true; -} - -uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t *self) { - return self->real_frequency; -} - -uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self) { - return self->phase; -} - -uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) { - return self->polarity; -} diff --git a/ports/broadcom/common-hal/busio/SPI.h b/ports/broadcom/common-hal/busio/SPI.h deleted file mode 100644 index 3d52b6b0a44..00000000000 --- a/ports/broadcom/common-hal/busio/SPI.h +++ /dev/null @@ -1,25 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "common-hal/microcontroller/Pin.h" - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; - bool has_lock; - const mcu_pin_obj_t *clock; - const mcu_pin_obj_t *MOSI; - const mcu_pin_obj_t *MISO; - uint32_t target_frequency; - int32_t real_frequency; - uint8_t polarity; - uint8_t phase; - uint8_t bits; - uint8_t index; -} busio_spi_obj_t; diff --git a/ports/broadcom/common-hal/busio/UART.c b/ports/broadcom/common-hal/busio/UART.c deleted file mode 100644 index 55c890475eb..00000000000 --- a/ports/broadcom/common-hal/busio/UART.c +++ /dev/null @@ -1,489 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 microDev -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/busio/UART.h" - -#include "py/stream.h" -#include "py/mperrno.h" -#include "py/runtime.h" -#include "supervisor/shared/tick.h" -#include "shared/runtime/interrupt_char.h" -#include "shared-bindings/microcontroller/Pin.h" - -#include "peripherals/broadcom/cpu.h" -#include "peripherals/broadcom/defines.h" -#include "peripherals/broadcom/gpio.h" -#include "peripherals/broadcom/interrupts.h" -#include "peripherals/broadcom/vcmailbox.h" - -#define NO_PIN 0xff - -// UART1 is a different peripheral than the rest so it is hardcoded below. -#if BCM_VERSION == 2711 -#define NUM_UART (6) -static ARM_UART_PL011_Type *uart[NUM_UART] = {UART0, NULL, UART2, UART3, UART4, UART5}; -#else -#define NUM_UART (2) -static ARM_UART_PL011_Type *uart[NUM_UART] = {UART0, NULL}; -#endif - -typedef enum { - STATUS_FREE = 0, - STATUS_BUSY, - STATUS_NEVER_RESET -} uart_status_t; - -static uart_status_t uart_status[NUM_UART]; -static busio_uart_obj_t *active_uart[NUM_UART]; - -void reset_uart(void) { - bool any_pl011_active = false; - for (uint8_t num = 0; num < NUM_UART; num++) { - if (uart_status[num] == STATUS_BUSY) { - if (num == 1) { - UART1->IER_b.DATA_READY = false; - UART1->CNTL = 0; - COMPLETE_MEMORY_READS; - AUX->ENABLES_b.UART_1 = false; - } else { - ARM_UART_PL011_Type *pl011 = uart[num]; - pl011->CR = 0; - } - active_uart[num] = NULL; - uart_status[num] = STATUS_FREE; - } else { - any_pl011_active = any_pl011_active || (num != 1 && uart_status[num] == STATUS_NEVER_RESET); - } - } - if (!any_pl011_active) { - BP_DisableIRQ(UART_IRQn); - } - COMPLETE_MEMORY_READS; - if (AUX->ENABLES == 0) { - BP_DisableIRQ(AUX_IRQn); - } -} - -static void fetch_all_from_fifo(busio_uart_obj_t *self) { - if (self->uart_id == 1) { - while (UART1->STAT_b.DATA_READY && ringbuf_num_empty(&self->ringbuf) > 0) { - int c = UART1->IO_b.DATA; - if (self->sigint_enabled && c == mp_interrupt_char) { - mp_sched_keyboard_interrupt(); - continue; - } - ringbuf_put(&self->ringbuf, c); - } - } else { - ARM_UART_PL011_Type *pl011 = uart[self->uart_id]; - while (!pl011->FR_b.RXFE && ringbuf_num_empty(&self->ringbuf) > 0) { - int c = pl011->DR_b.DATA; - if (self->sigint_enabled && c == mp_interrupt_char) { - mp_sched_keyboard_interrupt(); - continue; - } - ringbuf_put(&self->ringbuf, c); - } - } -} - -void UART1_IRQHandler(void) { - fetch_all_from_fifo(active_uart[1]); - // We couldn't read all pending data (overrun) so clear the FIFO so that the interrupt - // can finish. - if (UART1->STAT_b.DATA_READY) { - UART1->IIR_b.DATA_READY = 1; - } -} - -void pl011_IRQHandler(uint8_t index) { - fetch_all_from_fifo(active_uart[index]); - // Clear the interrupt in case we weren't able to clear it by emptying the - // FIFO. (This won't clear the FIFO.) - ARM_UART_PL011_Type *pl011 = uart[index]; - pl011->ICR = ARM_UART_PL011_ICR_RXIC_Msk; -} - -void UART0_IRQHandler(void) { - pl011_IRQHandler(0); -} - -#if BCM_VERSION == 2711 -void UART2_IRQHandler(void) { - pl011_IRQHandler(2); -} -void UART3_IRQHandler(void) { - pl011_IRQHandler(3); -} -void UART4_IRQHandler(void) { - pl011_IRQHandler(4); -} -void UART5_IRQHandler(void) { - pl011_IRQHandler(5); -} -#endif - -void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { - uart_status[self->uart_id] = STATUS_NEVER_RESET; - common_hal_never_reset_pin(self->tx_pin); - common_hal_never_reset_pin(self->rx_pin); - common_hal_never_reset_pin(self->cts_pin); - common_hal_never_reset_pin(self->rts_pin); -} - -void common_hal_busio_uart_construct(busio_uart_obj_t *self, - const mcu_pin_obj_t *tx, const mcu_pin_obj_t *rx, - const mcu_pin_obj_t *rts, const mcu_pin_obj_t *cts, - const mcu_pin_obj_t *rs485_dir, bool rs485_invert, - uint32_t baudrate, uint8_t bits, busio_uart_parity_t parity, uint8_t stop, - mp_float_t timeout, uint16_t receiver_buffer_size, byte *receiver_buffer, - bool sigint_enabled) { - - mp_arg_validate_int_max(bits, 8, MP_QSTR_bits); - mp_arg_validate_int_min(receiver_buffer_size, 1, MP_QSTR_receiver_buffer_size); - - if ((rs485_dir != NULL) || (rs485_invert)) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("RS485 Not yet supported on this device")); - } - - size_t instance_index = NUM_UART; - BP_Function_Enum tx_alt = 0; - BP_Function_Enum rx_alt = 0; - BP_Function_Enum rts_alt = 0; - BP_Function_Enum cts_alt = 0; - for (size_t i = 0; i < NUM_UART; i++) { - if (uart_status[i] != STATUS_FREE) { - continue; - } - if (tx != NULL) { - if (!pin_find_alt(tx, PIN_FUNCTION_UART, i, UART_FUNCTION_TXD, &tx_alt)) { - continue; - } - if (rts != NULL && !pin_find_alt(rts, PIN_FUNCTION_UART, i, UART_FUNCTION_RTS, &rts_alt)) { - continue; - } - } - if (rx != NULL) { - if (!pin_find_alt(rx, PIN_FUNCTION_UART, i, UART_FUNCTION_RXD, &rx_alt)) { - continue; - } - if (cts != NULL && !pin_find_alt(cts, PIN_FUNCTION_UART, i, UART_FUNCTION_CTS, &cts_alt)) { - continue; - } - } - instance_index = i; - break; - } - if (instance_index == NUM_UART) { - raise_ValueError_invalid_pins(); - } - - self->rx_pin = rx; - self->tx_pin = tx; - self->rts_pin = rts; - self->cts_pin = cts; - self->sigint_enabled = sigint_enabled; - - if (rx != NULL) { - // Use the provided buffer when given. - if (receiver_buffer != NULL) { - ringbuf_init(&self->ringbuf, receiver_buffer, receiver_buffer_size); - } else { - if (!ringbuf_alloc(&self->ringbuf, receiver_buffer_size)) { - m_malloc_fail(receiver_buffer_size); - } - } - } - - active_uart[self->uart_id] = self; - - ARM_UART_PL011_Type *pl011 = uart[self->uart_id]; - - if (self->uart_id == 1) { - AUX->ENABLES_b.UART_1 = true; - - UART1->IER = 0; - UART1->CNTL = 0; - if (bits == 8) { - UART1->LCR_b.DATA_SIZE = UART1_LCR_DATA_SIZE_MODE_8BIT; - } else if (bits == 7) { - UART1->LCR_b.DATA_SIZE = UART1_LCR_DATA_SIZE_MODE_7BIT; - } - UART1->MCR = 0; - UART1->IER = 0; - - // Clear interrupts - UART1->IIR = 0xff; - common_hal_busio_uart_set_baudrate(self, baudrate); - if (tx != NULL) { - UART1->CNTL |= UART1_CNTL_TX_ENABLE_Msk; - } - if (rx != NULL) { - UART1->CNTL |= UART1_CNTL_RX_ENABLE_Msk; - } - } else { - // Ensure the UART is disabled as we configure it. - pl011->CR_b.UARTEN = false; - pl011->IMSC = 0; - pl011->ICR = 0x3ff; - - common_hal_busio_uart_set_baudrate(self, baudrate); - - uint32_t line_control = ARM_UART_PL011_LCR_H_FEN_Msk; - line_control |= (bits - 5) << ARM_UART_PL011_LCR_H_WLEN_Pos; - if (stop == 2) { - line_control |= ARM_UART_PL011_LCR_H_STP2_Msk; - } - if (parity != BUSIO_UART_PARITY_NONE) { - line_control |= ARM_UART_PL011_LCR_H_PEN_Msk; - } - if (parity == BUSIO_UART_PARITY_EVEN) { - line_control |= ARM_UART_PL011_LCR_H_EPS_Msk; - } - pl011->LCR_H = line_control; - - uint32_t control = ARM_UART_PL011_CR_UARTEN_Msk; - if (tx != NULL) { - control |= ARM_UART_PL011_CR_TXE_Msk; - } - if (rx != NULL) { - control |= ARM_UART_PL011_CR_RXE_Msk; - } - if (cts != NULL) { - control |= ARM_UART_PL011_CR_CTSEN_Msk; - } - if (rts != NULL) { - control |= ARM_UART_PL011_CR_RTSEN_Msk; - } - pl011->CR = control; - } - - // Setup the pins after waiting for UART stuff - COMPLETE_MEMORY_READS; - if (tx != NULL) { - gpio_set_pull(tx->number, BP_PULL_NONE); - gpio_set_function(tx->number, tx_alt); - } - if (rx != NULL) { - gpio_set_pull(rx->number, BP_PULL_NONE); - gpio_set_function(rx->number, rx_alt); - } - if (rts != NULL) { - gpio_set_pull(rts->number, BP_PULL_NONE); - gpio_set_function(rts->number, rts_alt); - } - if (cts != NULL) { - gpio_set_pull(cts->number, BP_PULL_NONE); - gpio_set_function(cts->number, cts_alt); - } - - // Turn on interrupts - COMPLETE_MEMORY_READS; - if (self->uart_id == 1) { - UART1->IER_b.DATA_READY = true; - // Never disable this in case the SPIs are used. They can each be - // disabled at the peripheral itself. - BP_EnableIRQ(AUX_IRQn); - } else { - pl011->IMSC_b.RXIM = true; - // Never disable this in case the other PL011 UARTs are used. - BP_EnableIRQ(UART_IRQn); - } -} - -bool common_hal_busio_uart_deinited(busio_uart_obj_t *self) { - return self->tx_pin == NULL && self->rx_pin == NULL; -} - -void common_hal_busio_uart_deinit(busio_uart_obj_t *self) { - if (common_hal_busio_uart_deinited(self)) { - return; - } - if (self->uart_id == 1) { - UART1->IER_b.DATA_READY = false; - UART1->CNTL = 0; - AUX->ENABLES_b.UART_1 = false; - } else { - ARM_UART_PL011_Type *pl011 = uart[self->uart_id]; - pl011->CR = 0; - } - active_uart[self->uart_id] = NULL; - ringbuf_deinit(&self->ringbuf); - uart_status[self->uart_id] = STATUS_FREE; - common_hal_reset_pin(self->tx_pin); - common_hal_reset_pin(self->rx_pin); - common_hal_reset_pin(self->cts_pin); - common_hal_reset_pin(self->rts_pin); - self->tx_pin = NULL; - self->rx_pin = NULL; - self->cts_pin = NULL; - self->rts_pin = NULL; -} - -// Write characters. -size_t common_hal_busio_uart_write(busio_uart_obj_t *self, const uint8_t *data, size_t len, int *errcode) { - if (self->tx_pin == NULL) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("No %q pin"), MP_QSTR_tx); - } - - COMPLETE_MEMORY_READS; - ARM_UART_PL011_Type *pl011 = uart[self->uart_id]; - for (size_t i = 0; i < len; i++) { - if (self->uart_id == 1) { - // Wait for the FIFO to have space. - while (!UART1->STAT_b.TX_READY) { - RUN_BACKGROUND_TASKS; - } - UART1->IO = data[i]; - } else { - while (pl011->FR_b.TXFF) { - RUN_BACKGROUND_TASKS; - } - pl011->DR_b.DATA = data[i]; - } - } - // Wait for the data to be shifted out - if (self->uart_id == 1) { - while (!UART1->STAT_b.TX_DONE) { - RUN_BACKGROUND_TASKS; - } - } else { - while (pl011->FR_b.BUSY) { - RUN_BACKGROUND_TASKS; - } - } - COMPLETE_MEMORY_READS; - return len; -} - -static void disable_interrupt(busio_uart_obj_t *self) { - if (self->uart_id == 1) { - UART1->IER_b.DATA_READY = false; - } -} - -static void enable_interrupt(busio_uart_obj_t *self) { - if (self->uart_id == 1) { - UART1->IER_b.DATA_READY = true; - } -} - -// Read characters. -size_t common_hal_busio_uart_read(busio_uart_obj_t *self, uint8_t *data, size_t len, int *errcode) { - if (self->rx_pin == NULL) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("No %q pin"), MP_QSTR_rx); - } - - if (len == 0) { - // Nothing to read. - return 0; - } - COMPLETE_MEMORY_READS; - - // Prevent conflict with uart irq. - disable_interrupt(self); - - // Copy as much received data as available, up to len bytes. - size_t total_read = ringbuf_get_n(&self->ringbuf, data, len); - - // Check if we still need to read more data. - if (len > total_read) { - len -= total_read; - uint64_t start_ticks = supervisor_ticks_ms64(); - // Busy-wait until timeout or until we've read enough chars. - while (len > 0 && (supervisor_ticks_ms64() - start_ticks < self->timeout_ms)) { - fetch_all_from_fifo(self); - size_t additional_read = ringbuf_get_n(&self->ringbuf, data + total_read, len); - len -= additional_read; - total_read += additional_read; - if (additional_read > 0) { - // Reset the timeout on every character read. - start_ticks = supervisor_ticks_ms64(); - } - RUN_BACKGROUND_TASKS; - // Allow user to break out of a timeout with a KeyboardInterrupt. - if (mp_hal_is_interrupted()) { - break; - } - } - } - - // Now that we've emptied the ringbuf some, fill it up with anything in the - // FIFO. This ensures that we'll empty the FIFO as much as possible and - // reset the interrupt when we catch up. - fetch_all_from_fifo(self); - - // Re-enable irq. - enable_interrupt(self); - - COMPLETE_MEMORY_READS; - if (total_read == 0) { - *errcode = EAGAIN; - return MP_STREAM_ERROR; - } - - return total_read; -} - -uint32_t common_hal_busio_uart_get_baudrate(busio_uart_obj_t *self) { - return self->baudrate; -} - -void common_hal_busio_uart_set_baudrate(busio_uart_obj_t *self, uint32_t baudrate) { - if (self->uart_id == 1) { - uint32_t source_clock = vcmailbox_get_clock_rate(VCMAILBOX_CLOCK_CORE); - UART1->BAUD = ((source_clock / (baudrate * 8)) - 1); - } else { - ARM_UART_PL011_Type *pl011 = uart[self->uart_id]; - bool reenable = false; - if (pl011->CR_b.UARTEN) { - pl011->CR_b.UARTEN = false; - reenable = true; - } - uint32_t source_clock = vcmailbox_get_clock_rate_measured(VCMAILBOX_CLOCK_UART); - uint32_t divisor = 16 * baudrate; - pl011->IBRD = source_clock / divisor; - // The fractional divisor is 64ths. - uint32_t remainder = source_clock % divisor; - uint32_t per_tick = (divisor / 64) + 1; - uint32_t adjust = 0; - if (remainder % per_tick > 0) { - adjust = 1; - } - pl011->FBRD = remainder / per_tick + adjust; - if (reenable) { - pl011->CR_b.UARTEN = true; - } - } - self->baudrate = baudrate; -} - -mp_float_t common_hal_busio_uart_get_timeout(busio_uart_obj_t *self) { - return (mp_float_t)(self->timeout_ms / 1000.0L); -} - -void common_hal_busio_uart_set_timeout(busio_uart_obj_t *self, mp_float_t timeout) { - self->timeout_ms = timeout * 1000; -} - -uint32_t common_hal_busio_uart_rx_characters_available(busio_uart_obj_t *self) { - fetch_all_from_fifo(self); - return ringbuf_num_filled(&self->ringbuf); -} - -void common_hal_busio_uart_clear_rx_buffer(busio_uart_obj_t *self) { - ringbuf_clear(&self->ringbuf); -} - -bool common_hal_busio_uart_ready_to_tx(busio_uart_obj_t *self) { - if (self->tx_pin == NULL) { - return false; - } - if (self->uart_id == 1) { - return UART1->STAT_b.TX_READY; - } - return !uart[self->uart_id]->FR_b.TXFF; -} diff --git a/ports/broadcom/common-hal/busio/UART.h b/ports/broadcom/common-hal/busio/UART.h deleted file mode 100644 index 866e64c4855..00000000000 --- a/ports/broadcom/common-hal/busio/UART.h +++ /dev/null @@ -1,25 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 microDev -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/obj.h" -#include "py/ringbuf.h" - -typedef struct { - mp_obj_base_t base; - const mcu_pin_obj_t *tx_pin; - const mcu_pin_obj_t *rx_pin; - const mcu_pin_obj_t *cts_pin; - const mcu_pin_obj_t *rts_pin; - uint32_t baudrate; - uint32_t timeout_ms; - ringbuf_t ringbuf; - bool sigint_enabled; - uint8_t uart_id; -} busio_uart_obj_t; - -extern void reset_uart(void); diff --git a/ports/broadcom/common-hal/busio/__init__.c b/ports/broadcom/common-hal/busio/__init__.c deleted file mode 100644 index b726684324a..00000000000 --- a/ports/broadcom/common-hal/busio/__init__.c +++ /dev/null @@ -1,7 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -// No busio module functions. diff --git a/ports/broadcom/common-hal/digitalio/DigitalInOut.c b/ports/broadcom/common-hal/digitalio/DigitalInOut.c deleted file mode 100644 index b2b0aae404b..00000000000 --- a/ports/broadcom/common-hal/digitalio/DigitalInOut.c +++ /dev/null @@ -1,144 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2020 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include -#include - -#include "py/runtime.h" -#include "py/mphal.h" - -#include "common-hal/microcontroller/Pin.h" -#include "shared-bindings/digitalio/DigitalInOut.h" - -#include "peripherals/broadcom/gpio.h" - -digitalinout_result_t common_hal_digitalio_digitalinout_construct( - digitalio_digitalinout_obj_t *self, const mcu_pin_obj_t *pin) { - claim_pin(pin); - self->pin = pin; - self->output = false; - self->open_drain = false; - - return DIGITALINOUT_OK; -} - -void common_hal_digitalio_digitalinout_never_reset( - digitalio_digitalinout_obj_t *self) { - never_reset_pin_number(self->pin->number); -} - -bool common_hal_digitalio_digitalinout_deinited(digitalio_digitalinout_obj_t *self) { - return self->pin == NULL; -} - -void common_hal_digitalio_digitalinout_deinit(digitalio_digitalinout_obj_t *self) { - if (common_hal_digitalio_digitalinout_deinited(self)) { - return; - } - reset_pin_number(self->pin->number); - self->pin = NULL; -} - -digitalinout_result_t common_hal_digitalio_digitalinout_switch_to_input( - digitalio_digitalinout_obj_t *self, digitalio_pull_t pull) { - self->output = false; - // This also sets direction to input. - common_hal_digitalio_digitalinout_set_pull(self, pull); - return DIGITALINOUT_OK; -} - -digitalinout_result_t common_hal_digitalio_digitalinout_switch_to_output( - digitalio_digitalinout_obj_t *self, bool value, - digitalio_drive_mode_t drive_mode) { - const uint8_t pin = self->pin->number; - gpio_set_pull(pin, PULL_NONE); - - self->output = true; - self->open_drain = drive_mode == DRIVE_MODE_OPEN_DRAIN; - - // Pin direction is ultimately set in set_value. We don't need to do it here. - common_hal_digitalio_digitalinout_set_value(self, value); - return DIGITALINOUT_OK; -} - -digitalio_direction_t common_hal_digitalio_digitalinout_get_direction( - digitalio_digitalinout_obj_t *self) { - return self->output ? DIRECTION_OUTPUT : DIRECTION_INPUT; -} - -void common_hal_digitalio_digitalinout_set_value( - digitalio_digitalinout_obj_t *self, bool value) { - const uint8_t pin = self->pin->number; - if (self->open_drain && value) { - // If true and open-drain, set the direction -before- setting - // the pin value, to to avoid a high glitch on the pin before - // switching from output to input for open-drain. - gpio_set_function(pin, GPIO_FUNCTION_INPUT); - gpio_set_value(pin, value); - } else { - // Otherwise set the direction -after- setting the pin value, - // to avoid a glitch which might occur if the old value was - // different and the pin was previously set to input. - gpio_set_value(pin, value); - gpio_set_function(pin, GPIO_FUNCTION_OUTPUT); - } -} - -bool common_hal_digitalio_digitalinout_get_value( - digitalio_digitalinout_obj_t *self) { - return gpio_get_value(self->pin->number); -} - -digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode( - digitalio_digitalinout_obj_t *self, - digitalio_drive_mode_t drive_mode) { - bool value = common_hal_digitalio_digitalinout_get_value(self); - self->open_drain = drive_mode == DRIVE_MODE_OPEN_DRAIN; - // True is implemented differently between modes so reset the value to make - // sure it's correct for the new mode. - if (value) { - common_hal_digitalio_digitalinout_set_value(self, value); - } - return DIGITALINOUT_OK; -} - -digitalio_drive_mode_t common_hal_digitalio_digitalinout_get_drive_mode( - digitalio_digitalinout_obj_t *self) { - if (self->open_drain) { - return DRIVE_MODE_OPEN_DRAIN; - } else { - return DRIVE_MODE_PUSH_PULL; - } -} - -digitalinout_result_t common_hal_digitalio_digitalinout_set_pull( - digitalio_digitalinout_obj_t *self, digitalio_pull_t pull) { - const uint8_t pin = self->pin->number; - BP_PULL_Enum bp_pull = BP_PULL_NONE; - if (pull == PULL_UP) { - bp_pull = BP_PULL_UP; - } else if (pull == PULL_DOWN) { - bp_pull = BP_PULL_DOWN; - } - gpio_set_pull(pin, bp_pull); - return DIGITALINOUT_OK; -} - -digitalio_pull_t common_hal_digitalio_digitalinout_get_pull( - digitalio_digitalinout_obj_t *self) { - uint32_t pin = self->pin->number; - if (self->output) { - mp_raise_AttributeError(MP_ERROR_TEXT("Cannot get pull while in output mode")); - return PULL_NONE; - } else { - if (gpio_get_pull(pin) == BP_PULL_UP) { - return PULL_UP; - } else if (gpio_get_pull(pin) == BP_PULL_DOWN) { - return PULL_DOWN; - } - } - return PULL_NONE; -} diff --git a/ports/broadcom/common-hal/digitalio/__init__.c b/ports/broadcom/common-hal/digitalio/__init__.c deleted file mode 100644 index fa222ed01f0..00000000000 --- a/ports/broadcom/common-hal/digitalio/__init__.c +++ /dev/null @@ -1,7 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -// No digitalio module functions. diff --git a/ports/broadcom/common-hal/microcontroller/Pin.c b/ports/broadcom/common-hal/microcontroller/Pin.c deleted file mode 100644 index be5c0ff6210..00000000000 --- a/ports/broadcom/common-hal/microcontroller/Pin.c +++ /dev/null @@ -1,89 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/microcontroller/Pin.h" - -#include "peripherals/broadcom/gpio.h" - -static bool pin_in_use[BCM_PIN_COUNT]; -static bool never_reset_pin[BCM_PIN_COUNT]; - -void reset_all_pins(void) { - for (size_t i = 0; i < BCM_PIN_COUNT; i++) { - if (never_reset_pin[i]) { - continue; - } - reset_pin_number(i); - } -} - -void never_reset_pin_number(uint8_t pin_number) { - never_reset_pin[pin_number] = true; -} - -void reset_pin_number(uint8_t pin_number) { - pin_in_use[pin_number] = false; - never_reset_pin[pin_number] = false; - // Reset JTAG pins back to JTAG. - BP_PULL_Enum pull = BP_PULL_NONE; - if (22 <= pin_number && pin_number <= 27) { - gpio_set_function(pin_number, GPIO_FUNCTION_ALT4); - } else { - gpio_set_function(pin_number, GPIO_FUNCTION_INPUT); - } - // Set the pull to match the datasheet. - if (pin_number < 9 || - (33 < pin_number && pin_number < 37) || - pin_number > 45) { - pull = BP_PULL_UP; - } else if (pin_number != 28 && - pin_number != 29 && - pin_number != 44 && - pin_number != 45) { - // Most pins are pulled low so we only exclude the four pins that aren't - // pulled at all. This will also set the JTAG pins 22-27 - pull = BP_PULL_DOWN; - } - gpio_set_pull(pin_number, pull); -} - -void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - if (pin == NULL) { - return; - } - never_reset_pin_number(pin->number); -} - -void common_hal_reset_pin(const mcu_pin_obj_t *pin) { - if (pin == NULL) { - return; - } - reset_pin_number(pin->number); -} - -void claim_pin(const mcu_pin_obj_t *pin) { - pin_in_use[pin->number] = true; -} - -bool pin_number_is_free(uint8_t pin_number) { - return !pin_in_use[pin_number]; -} - -bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { - return pin_number_is_free(pin->number); -} - -uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin) { - return pin->number; -} - -void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { - return claim_pin(pin); -} - -void common_hal_mcu_pin_reset_number(uint8_t pin_no) { - reset_pin_number(pin_no); -} diff --git a/ports/broadcom/common-hal/microcontroller/Pin.h b/ports/broadcom/common-hal/microcontroller/Pin.h deleted file mode 100644 index 4e0e2e2e659..00000000000 --- a/ports/broadcom/common-hal/microcontroller/Pin.h +++ /dev/null @@ -1,22 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include -#include - -#include - -#include "peripherals/broadcom/pins.h" - -void reset_all_pins(void); -// reset_pin_number takes the pin number instead of the pointer so that objects don't -// need to store a full pointer. -void reset_pin_number(uint8_t pin_number); -void never_reset_pin_number(uint8_t pin_number); -void claim_pin(const mcu_pin_obj_t *pin); -bool pin_number_is_free(uint8_t pin_number); diff --git a/ports/broadcom/common-hal/microcontroller/Processor.c b/ports/broadcom/common-hal/microcontroller/Processor.c deleted file mode 100644 index 94eafe6c3d1..00000000000 --- a/ports/broadcom/common-hal/microcontroller/Processor.c +++ /dev/null @@ -1,35 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/microcontroller/Processor.h" - -#include - -#include "peripherals/broadcom/vcmailbox.h" - -float common_hal_mcu_processor_get_temperature(void) { - uint32_t temp = vcmailbox_get_temperature(); - return temp / 1000.0f; -} - -float common_hal_mcu_processor_get_voltage(void) { - return NAN; -} - -uint32_t common_hal_mcu_processor_get_frequency(void) { - return vcmailbox_get_clock_rate_measured(VCMAILBOX_CLOCK_ARM); -} - -void common_hal_mcu_processor_get_uid(uint8_t raw_id[]) { - uint64_t serial = vcmailbox_get_serial_number(); - for (size_t i = 0; i < sizeof(uint64_t); i++) { - raw_id[i] = (serial >> (64 - ((i + 1) * 8))) & 0xff; - } -} - -mcu_reset_reason_t common_hal_mcu_processor_get_reset_reason(void) { - return MCU_RESET_REASON_UNKNOWN; -} diff --git a/ports/broadcom/common-hal/microcontroller/Processor.h b/ports/broadcom/common-hal/microcontroller/Processor.h deleted file mode 100644 index 0c9088f1881..00000000000 --- a/ports/broadcom/common-hal/microcontroller/Processor.h +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; - // Stores no state currently. -} mcu_processor_obj_t; - -#define COMMON_HAL_MCU_PROCESSOR_UID_LENGTH 8 diff --git a/ports/broadcom/common-hal/microcontroller/__init__.c b/ports/broadcom/common-hal/microcontroller/__init__.c deleted file mode 100644 index 279be26f91a..00000000000 --- a/ports/broadcom/common-hal/microcontroller/__init__.c +++ /dev/null @@ -1,180 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/microcontroller/__init__.h" - -#include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/microcontroller/Processor.h" -#include "common-hal/microcontroller/__init__.h" -#include "peripherals/broadcom/defines.h" -#include "peripherals/broadcom/interrupts.h" -#include "supervisor/port.h" - -#include "mphalport.h" - -void common_hal_mcu_delay_us(uint32_t delay) { - mp_hal_delay_us(delay); -} - -volatile uint32_t nesting_count = 0; -void common_hal_mcu_disable_interrupts(void) { - BP_DisableIRQs(); - nesting_count++; -} - -void common_hal_mcu_enable_interrupts(void) { - if (nesting_count == 0) { - // reset_into_safe_mode(SAFE_MODE_INTERRUPT_ERROR); - } - nesting_count--; - if (nesting_count > 0) { - return; - } - BP_EnableIRQs(); -} - -void common_hal_mcu_on_next_reset(mcu_runmode_t runmode) { -} - -void common_hal_mcu_reset(void) { - reset_cpu(); -} - -// The singleton microcontroller.Processor object, bound to microcontroller.cpu -// It currently only has properties, and no state. -#if CIRCUITPY_PROCESSOR_COUNT > 1 -static const mcu_processor_obj_t processor0 = { - .base = { - .type = &mcu_processor_type, - }, -}; - -static const mcu_processor_obj_t processor1 = { - .base = { - .type = &mcu_processor_type, - }, -}; - -static const mcu_processor_obj_t processor2 = { - .base = { - .type = &mcu_processor_type, - }, -}; - -static const mcu_processor_obj_t processor3 = { - .base = { - .type = &mcu_processor_type, - }, -}; - -const mp_rom_obj_tuple_t common_hal_multi_processor_obj = { - {&mp_type_tuple}, - CIRCUITPY_PROCESSOR_COUNT, - { - MP_ROM_PTR(&processor0), - MP_ROM_PTR(&processor1), - MP_ROM_PTR(&processor2), - MP_ROM_PTR(&processor3) - } -}; -#endif - -const mcu_processor_obj_t common_hal_mcu_processor_obj = { - .base = { - .type = &mcu_processor_type, - }, -}; - -#if CIRCUITPY_NVM && CIRCUITPY_INTERNAL_NVM_SIZE > 0 -// The singleton nvm.ByteArray object. -const nvm_bytearray_obj_t common_hal_mcu_nvm_obj = { - .base = { - .type = &nvm_bytearray_type, - }, - .len = CIRCUITPY_INTERNAL_NVM_SIZE, - .start_address = (uint8_t *)(CIRCUITPY_INTERNAL_NVM_START_ADDR) -}; -#endif - -#if CIRCUITPY_WATCHDOG -// The singleton watchdog.WatchDogTimer object. -watchdog_watchdogtimer_obj_t common_hal_mcu_watchdogtimer_obj = { - .base = { - .type = &watchdog_watchdogtimer_type, - }, - .timeout = 0.0f, - .mode = WATCHDOGMODE_NONE, -}; -#endif - -// This maps MCU pin names to pin objects. -#define PIN(num) { MP_ROM_QSTR(MP_QSTR_GPIO##num), MP_ROM_PTR(&pin_GPIO##num) }, -const mp_rom_map_elem_t mcu_pin_global_dict_table[TOTAL_GPIO_COUNT] = { - PIN(0) - PIN(1) - PIN(2) - PIN(3) - PIN(4) - PIN(5) - PIN(6) - PIN(7) - PIN(8) - PIN(9) - PIN(10) - PIN(11) - PIN(12) - PIN(13) - PIN(14) - PIN(15) - PIN(16) - PIN(17) - PIN(18) - PIN(19) - PIN(20) - PIN(21) - PIN(22) - PIN(23) - PIN(24) - PIN(25) - PIN(26) - PIN(27) - PIN(28) - PIN(29) - - PIN(30) - PIN(31) - PIN(32) - PIN(33) - PIN(34) - PIN(35) - PIN(36) - PIN(37) - PIN(38) - PIN(39) - - PIN(40) - PIN(41) - PIN(42) - PIN(43) - PIN(44) - PIN(45) - PIN(46) - PIN(47) - PIN(48) - PIN(49) - - PIN(50) - PIN(51) - PIN(52) - PIN(53) - #if BCM_VERSION == 2711 - PIN(54) - PIN(55) - PIN(56) - PIN(57) - #endif -}; -MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_global_dict_table); diff --git a/ports/broadcom/common-hal/neopixel_write/__init__.c b/ports/broadcom/common-hal/neopixel_write/__init__.c deleted file mode 100644 index 175caf54041..00000000000 --- a/ports/broadcom/common-hal/neopixel_write/__init__.c +++ /dev/null @@ -1,165 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/neopixel_write/__init__.h" - -#include "py/runtime.h" - -#include "shared-bindings/microcontroller/__init__.h" -#include "shared-bindings/digitalio/DigitalInOut.h" -#include "shared-bindings/time/__init__.h" - -#include "peripherals/broadcom/cpu.h" - -#include "supervisor/port.h" - -uint64_t next_start_raw_ticks = 0; - -// NeoPixels are 800khz bit streams. Zeroes are 1/3 duty cycle (~416ns) and ones -// are 2/3 duty cycle (~833ns). - -void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, - uint8_t *pixels, uint32_t num_bytes) { - // Wait to make sure we don't append onto the last transmission. This should only be a tick or - // two. - int icnt; - while ((port_get_raw_ticks(NULL) < next_start_raw_ticks) && - (next_start_raw_ticks - port_get_raw_ticks(NULL) < 100)) { - RUN_BACKGROUND_TASKS; - } - - BP_Function_Enum alt_function = GPIO_FUNCTION_OUTPUT; - uint8_t index = 0; - uint8_t channel = 0; - bool found = false; - for (size_t i = 0; i < NUM_ALT_FUNC; i++) { - const pin_function_t *f = &digitalinout->pin->functions[i]; - if (f->type == PIN_FUNCTION_PWM) { - index = f->index; - channel = f->function; - alt_function = FSEL_VALUES[i]; - found = true; - break; - } - } - if (!found) { - mp_raise_ValueError(MP_ERROR_TEXT("NeoPixel not supported on pin")); - return; - } - - // Turn on the PWM clock. The speed is NeoPixel specific. - if (CM_PWM->CS_b.BUSY == 0) { - uint32_t source_clock; - #if BCM_VERSION == 2711 - source_clock = 54000000; - #else - source_clock = 19200000; - #endif - // Three clocks per 800khz bit to get the 1/3 or 2/3 timing. - uint32_t target_clock = 3 * 800000; - uint32_t int_div = source_clock / target_clock; - - CM_PWM->DIV = CM_PCM_DIV_PASSWD_PASSWD << CM_PCM_DIV_PASSWD_Pos | - (int_div) << CM_PCM_DIV_DIVI_Pos; - - CM_PWM->CS = CM_PCM_CS_PASSWD_PASSWD << CM_PCM_CS_PASSWD_Pos | - CM_PCM_CS_SRC_XOSC << CM_PCM_CS_SRC_Pos; - - // Set enable after setting the source to ensure it is stable. - CM_PWM->CS = CM_PCM_CS_PASSWD_PASSWD << CM_PCM_CS_PASSWD_Pos | - CM_PCM_CS_SRC_XOSC << CM_PCM_CS_SRC_Pos | - CM_PCM_CS_ENAB_Msk; - - // Wait for the clock to start up. - COMPLETE_MEMORY_READS; - icnt = 0; - while ((CM_PWM->CS_b.BUSY == 0) && (icnt++ < 1000)) { - } - } - - PWM0_Type *pwm = PWM0; - #if BCM_VERSION == 2711 - if (index == 1) { - pwm = PWM1; - } - #else - (void)index; - #endif - - pwm->RNG1 = 24; - pwm->RNG2 = 24; - COMPLETE_MEMORY_READS; - pwm->CTL = PWM0_CTL_CLRF1_Msk; - COMPLETE_MEMORY_READS; - - // Even though we're only transmitting one channel, we enable both. Without - // the second channel enabled, the output is repeated forever. - pwm->CTL = - PWM0_CTL_USEF2_Msk | - PWM0_CTL_MODE2_Msk | - PWM0_CTL_USEF1_Msk | - PWM0_CTL_MODE1_Msk; - - COMPLETE_MEMORY_READS; - pwm->CTL |= PWM0_CTL_PWEN1_Msk | PWM0_CTL_PWEN2_Msk; - - gpio_set_function(digitalinout->pin->number, alt_function); - - for (size_t i = 0; i < num_bytes; i++) { - uint32_t expanded = 0; - for (size_t j = 0; j < 8; j++) { - expanded = expanded >> 3; - if ((pixels[i] & (1 << j)) != 0) { - expanded |= 0xc0000000; - } else { - expanded |= 0x80000000; - } - } - if (channel == 1) { - icnt = 0; - while ((pwm->STA_b.FULL1 == 1) && (icnt++ < 150)) { - RUN_BACKGROUND_TASKS; - } - // Dummy value for the first channel. - pwm->FIF1 = 0x000000; - } - icnt = 0; - while ((pwm->STA_b.FULL1 == 1) && (icnt++ < 150)) { - RUN_BACKGROUND_TASKS; - } - pwm->FIF1 = expanded; - if (channel == 0) { - icnt = 0; - while ((pwm->STA_b.FULL1 == 1) && (icnt++ < 150)) { - RUN_BACKGROUND_TASKS; - } - // Dummy value for the second channel. - pwm->FIF1 = 0x000000; - } - } - - icnt = 0; - while ((pwm->STA_b.EMPT1 == 0) && (icnt++ < 2500)) { - RUN_BACKGROUND_TASKS; - } - // Wait for transmission to start. - icnt = 0; - while (((pwm->STA_b.STA1 == 0) && (pwm->STA_b.STA2 == 0)) && (icnt++ < 150)) { - RUN_BACKGROUND_TASKS; - } - // Wait for transmission to complete. - icnt = 0; - while (((pwm->STA_b.STA1 == 1) || (pwm->STA_b.STA2 == 1)) && (icnt++ < 150)) { - RUN_BACKGROUND_TASKS; - } - // Shouldn't be anything left in queue but clear it so the clock doesn't crash if there is - pwm->CTL = PWM0_CTL_CLRF1_Msk; - - gpio_set_function(digitalinout->pin->number, GPIO_FUNCTION_OUTPUT); - - // Update the next start. - next_start_raw_ticks = port_get_raw_ticks(NULL) + 1; -} diff --git a/ports/broadcom/common-hal/os/__init__.c b/ports/broadcom/common-hal/os/__init__.c deleted file mode 100644 index c1c50234451..00000000000 --- a/ports/broadcom/common-hal/os/__init__.c +++ /dev/null @@ -1,15 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "genhdr/mpversion.h" -#include "py/mpconfig.h" -#include "py/objstr.h" -#include "py/objtuple.h" -#include "py/qstr.h" - -bool common_hal_os_urandom(uint8_t *buffer, uint32_t length) { - return false; -} diff --git a/ports/broadcom/common-hal/rtc/RTC.c b/ports/broadcom/common-hal/rtc/RTC.c deleted file mode 100644 index c7092edd3fe..00000000000 --- a/ports/broadcom/common-hal/rtc/RTC.c +++ /dev/null @@ -1,38 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2019 Nick Moore for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2019 Artur Pacholec -// -// SPDX-License-Identifier: MIT - -#include - -#include "py/obj.h" -#include "py/runtime.h" -#include "shared/timeutils/timeutils.h" -#include "supervisor/port.h" - -// This is the time in seconds since 2000 that the RTC was started. -// TODO: Change the offset to ticks so that it can be a subsecond adjustment. -static uint32_t rtc_offset = 0; - -void common_hal_rtc_get_time(timeutils_struct_time_t *tm) { - uint64_t ticks_s = port_get_raw_ticks(NULL) / 1024; - timeutils_seconds_since_2000_to_struct_time(rtc_offset + ticks_s, tm); -} - -void common_hal_rtc_set_time(timeutils_struct_time_t *tm) { - uint64_t ticks_s = port_get_raw_ticks(NULL) / 1024; - uint32_t epoch_s = timeutils_seconds_since_2000( - tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec - ); - rtc_offset = epoch_s - ticks_s; -} - -int common_hal_rtc_get_calibration(void) { - return 0; -} - -void common_hal_rtc_set_calibration(int calibration) { - mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); -} diff --git a/ports/broadcom/common-hal/rtc/RTC.h b/ports/broadcom/common-hal/rtc/RTC.h deleted file mode 100644 index 9f310488793..00000000000 --- a/ports/broadcom/common-hal/rtc/RTC.h +++ /dev/null @@ -1,10 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2018 Noralf Trønnes -// SPDX-FileCopyrightText: Copyright (c) 2019 Artur Pacholec -// -// SPDX-License-Identifier: MIT - -#pragma once - -extern void rtc_reset(void); diff --git a/ports/broadcom/common-hal/rtc/__init__.c b/ports/broadcom/common-hal/rtc/__init__.c deleted file mode 100644 index 72d32ef2b2c..00000000000 --- a/ports/broadcom/common-hal/rtc/__init__.c +++ /dev/null @@ -1,5 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT diff --git a/ports/broadcom/common-hal/sdioio/SDCard.c b/ports/broadcom/common-hal/sdioio/SDCard.c deleted file mode 100644 index 2c9e6a2bb58..00000000000 --- a/ports/broadcom/common-hal/sdioio/SDCard.c +++ /dev/null @@ -1,432 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2020 Jeff Epler for Adafruit Industries -// -// SPDX-License-Identifier: MIT -#include - -#include "mphalport.h" - -#include "py/mperrno.h" -#include "py/runtime.h" - -#include "common-hal/microcontroller/Pin.h" -#include "extmod/vfs.h" -#include "shared-bindings/sdioio/SDCard.h" -#include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/microcontroller/__init__.h" -#include "supervisor/port.h" - -#include "peripherals/broadcom/cpu.h" -#include "peripherals/broadcom/defines.h" -#include "peripherals/broadcom/gpio.h" - -#include "lib/sdmmc/include/sdmmc_cmd.h" - -void osal_task_delay(uint32_t msec) { - mp_hal_delay_ms(msec); -} - -/*!< Host function to initialize the driver */ -static sdmmc_err_t _init(void) { - return SDMMC_OK; -} - -/*!< host function to set bus width */ -static sdmmc_err_t _set_bus_width(int slot, size_t width) { - if (width == 4) { - EMMC->CONTROL0_b.HCTL_DWIDTH = true; - } else if (width == 8) { - EMMC->CONTROL0_b.HCTL_8BIT = true; - } - return SDMMC_OK; -} - -/*!< host function to get the maximum bus width of a particular slot */ -static size_t _get_bus_width(int slot) { - return 4; -} - -/*!< host function to set card clock frequency */ -static sdmmc_err_t _set_card_clk(int slot, uint32_t freq_khz) { - uint32_t base_clock = 125000000; - uint32_t frequency = freq_khz * 1000; - uint64_t start_ticks = port_get_raw_ticks(NULL); - while ((EMMC->STATUS_b.CMD_INHIBIT || EMMC->STATUS_b.DAT_INHIBIT) && (port_get_raw_ticks(NULL) - start_ticks) < 1024) { - } - if (EMMC->STATUS_b.CMD_INHIBIT || EMMC->STATUS_b.DAT_INHIBIT) { - return SDMMC_ERR_TIMEOUT; - } - - EMMC->CONTROL1_b.CLK_EN = false; - - uint32_t divisor = base_clock / frequency; - // + 1 to ensure we round the frequency down. - if (base_clock % frequency > 0) { - divisor += 1; - } - // Only even divisors are supported - uint32_t register_value = divisor / 2; - if (divisor % 2 == 1) { - register_value += 1; - } - - // The divisor is stored with bits swapped. - EMMC->CONTROL1_b.CLK_FREQ8 = divisor & 0xff; - EMMC->CONTROL1_b.CLK_FREQ_MS2 = (divisor >> 8) & 0x3; - - EMMC->CONTROL1_b.CLK_EN = true; - start_ticks = port_get_raw_ticks(NULL); - while (!EMMC->CONTROL1_b.CLK_STABLE && (port_get_raw_ticks(NULL) - start_ticks) < 1024) { - } - if (!EMMC->CONTROL1_b.CLK_STABLE) { - return SDMMC_ERR_TIMEOUT; - } - return SDMMC_OK; -} - -/*!< host function to do a transaction */ -static sdmmc_err_t _do_transaction(int slot, sdmmc_command_t *cmdinfo) { - if (EMMC->STATUS_b.CMD_INHIBIT) { - return SDMMC_ERR_BUSY; - } - - // Clear interrupts and then send the command - EMMC->INTERRUPT = 0xffffffff; - EMMC->ARG1 = cmdinfo->arg; - - uint32_t cmd_flags = 0; - bool read = (cmdinfo->flags & SCF_CMD_READ) != 0; - if (SCF_CMD(cmdinfo->flags) == SCF_CMD_ADTC || - SCF_CMD(cmdinfo->flags) == (SCF_CMD_ADTC | SCF_CMD_READ)) { - if (EMMC->STATUS_b.DAT_INHIBIT) { - return SDMMC_ERR_BUSY; - } - cmd_flags = Arasan_EMMC_Distributor_CMDTM_TM_BLKCNT_EN_Msk | Arasan_EMMC_Distributor_CMDTM_CMD_ISDATA_Msk; - if (cmdinfo->datalen > cmdinfo->blklen) { - cmd_flags |= Arasan_EMMC_Distributor_CMDTM_TM_MULTI_BLOCK_Msk; - if ((cmdinfo->flags & SCF_AUTO_STOP) != 0) { - cmd_flags |= 1 << Arasan_EMMC_Distributor_CMDTM_TM_AUTO_CMD_EN_Pos; - } - } - if (read) { - cmd_flags |= Arasan_EMMC_Distributor_CMDTM_TM_DAT_DIR_Msk; - } - EMMC->BLKSIZECNT = (cmdinfo->datalen / cmdinfo->blklen) << Arasan_EMMC_Distributor_BLKSIZECNT_BLKCNT_Pos | - cmdinfo->blklen << Arasan_EMMC_Distributor_BLKSIZECNT_BLKSIZE_Pos; - } - - uint32_t response_type = EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_48BITS; - uint32_t crc = 0; - if ((cmdinfo->flags & SCF_RSP_CRC) != 0) { - crc |= Arasan_EMMC_Distributor_CMDTM_CMD_CRCCHK_EN_Msk; - } - if ((cmdinfo->flags & SCF_RSP_IDX) != 0) { - crc |= Arasan_EMMC_Distributor_CMDTM_CMD_IXCHK_EN_Msk; - } - if ((cmdinfo->flags & SCF_RSP_136) != 0) { - response_type = EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_136BITS; - } else if ((cmdinfo->flags & SCF_RSP_BSY) != 0) { - response_type = EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_48BITS_USING_BUSY; - } else if ((cmdinfo->flags & SCF_RSP_PRESENT) == 0) { - response_type = EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_NONE; - } - uint32_t full_cmd = cmd_flags | crc | - cmdinfo->opcode << Arasan_EMMC_Distributor_CMDTM_CMD_INDEX_Pos | - response_type << Arasan_EMMC_Distributor_CMDTM_CMD_RSPNS_TYPE_Pos; - EMMC->CMDTM = full_cmd; - - // Wait for an interrupt to indicate completion of the command. - uint64_t start_ticks = port_get_raw_ticks(NULL); - while (EMMC->INTERRUPT == 0 && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) { - } - if (!EMMC->INTERRUPT_b.CMD_DONE) { - cmdinfo->response[0] = 0; - // Reset the command circuit - EMMC->CONTROL1_b.SRST_CMD = true; - // Wait for the bit to clear - start_ticks = port_get_raw_ticks(NULL); - while (EMMC->CONTROL1_b.SRST_CMD && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) { - } - return SDMMC_ERR_TIMEOUT; - } else { - EMMC->INTERRUPT = Arasan_EMMC_Distributor_INTERRUPT_CMD_DONE_Msk; - } - - // Transfer the data. - // TODO: Use DMA - if (cmd_flags != 0) { - if (read) { - for (size_t i = 0; i < cmdinfo->datalen / sizeof(uint32_t); i++) { - start_ticks = port_get_raw_ticks(NULL); - while (!EMMC->INTERRUPT_b.READ_RDY && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) { - } - if (!EMMC->INTERRUPT_b.READ_RDY) { - return SDMMC_ERR_TIMEOUT; - } - ((uint32_t *)cmdinfo->data)[i] = EMMC->DATA; - } - } else { - for (size_t i = 0; i < cmdinfo->datalen / sizeof(uint32_t); i++) { - start_ticks = port_get_raw_ticks(NULL); - while (!EMMC->INTERRUPT_b.WRITE_RDY && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) { - } - if (!EMMC->INTERRUPT_b.WRITE_RDY) { - return SDMMC_ERR_TIMEOUT; - } - EMMC->DATA = ((uint32_t *)cmdinfo->data)[i]; - } - } - uint32_t data_done_mask = Arasan_EMMC_Distributor_INTERRUPT_ERR_Msk | Arasan_EMMC_Distributor_INTERRUPT_DATA_DONE_Msk; - start_ticks = port_get_raw_ticks(NULL); - while ((EMMC->INTERRUPT & data_done_mask) == 0 && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) { - } - if (!EMMC->INTERRUPT_b.DATA_DONE) { - cmdinfo->response[0] = 0; - // Reset the command circuit - EMMC->CONTROL1_b.SRST_DATA = true; - // Wait for the bit to clear - start_ticks = port_get_raw_ticks(NULL); - while (EMMC->CONTROL1_b.SRST_DATA && (port_get_raw_ticks(NULL) - start_ticks) < (size_t)cmdinfo->timeout_ms) { - } - return SDMMC_ERR_TIMEOUT; - } - } - - cmdinfo->response[0] = EMMC->RESP0; - if (response_type == EMMC_CMDTM_CMD_RSPNS_TYPE_RESPONSE_136BITS) { - // Rotate one more byte for some reason. - cmdinfo->response[3] = EMMC->RESP3 << 8 | ((EMMC->RESP2 >> 24) & 0xff); - cmdinfo->response[2] = EMMC->RESP2 << 8 | ((EMMC->RESP1 >> 24) & 0xff); - cmdinfo->response[1] = EMMC->RESP1 << 8 | ((EMMC->RESP0 >> 24) & 0xff); - cmdinfo->response[0] <<= 8; - } - return SDMMC_OK; -} - -/*!< host function to deinitialize the driver, called with the `slot` */ -static sdmmc_err_t _deinit(int slot) { - return SDMMC_OK; -} - -void common_hal_sdioio_sdcard_construct(sdioio_sdcard_obj_t *self, - const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, - uint8_t num_data, const mcu_pin_obj_t **data, uint32_t frequency) { - - if (clock != NULL) { - gpio_set_function(clock->number, GPIO_GPFSEL4_FSEL48_SD1_CLK); - gpio_set_pull(clock->number, BP_PULL_NONE); - self->clock_pin = clock->number; - gpio_set_function(command->number, GPIO_GPFSEL4_FSEL49_SD1_CMD); - gpio_set_pull(command->number, BP_PULL_UP); - self->command_pin = command->number; - gpio_set_function(data[0]->number, GPIO_GPFSEL5_FSEL50_SD1_DAT0); - gpio_set_pull(data[0]->number, BP_PULL_UP); - self->data_pins[0] = data[0]->number; - gpio_set_function(data[1]->number, GPIO_GPFSEL5_FSEL51_SD1_DAT1); - gpio_set_pull(data[1]->number, BP_PULL_UP); - self->data_pins[1] = data[1]->number; - gpio_set_function(data[2]->number, GPIO_GPFSEL5_FSEL52_SD1_DAT2); - gpio_set_pull(data[2]->number, BP_PULL_UP); - self->data_pins[2] = data[2]->number; - gpio_set_function(data[3]->number, GPIO_GPFSEL5_FSEL53_SD1_DAT3); - gpio_set_pull(data[3]->number, BP_PULL_UP); - self->data_pins[3] = data[3]->number; - } else { - // Switch the sdcard to use the old arasan interface. - GPIO->EXTRA_MUX_b.SDIO = GPIO_EXTRA_MUX_SDIO_ARASAN; - } - - COMPLETE_MEMORY_READS; - - self->host_info = (sdmmc_host_t) { - .flags = SDMMC_HOST_FLAG_1BIT | SDMMC_HOST_FLAG_4BIT | SDMMC_HOST_FLAG_DEINIT_ARG, - .slot = 0, - .max_freq_khz = SDMMC_FREQ_HIGHSPEED, - .io_voltage = 3.3f, - .command_timeout_ms = 0, - .init = _init, - .set_bus_width = _set_bus_width, - .get_bus_width = _get_bus_width, - .set_bus_ddr_mode = NULL, - .set_card_clk = _set_card_clk, - .do_transaction = _do_transaction, - .deinit_p = _deinit, - }; - - // Reset the peripheral. - EMMC->CONTROL0 = 0; - EMMC->CONTROL1_b.SRST_HC = true; - // Wait for the bit to clear - while (EMMC->CONTROL1_b.SRST_HC) { - - } - // Set max timeout - EMMC->CONTROL1 |= Arasan_EMMC_Distributor_CONTROL1_CLK_INTLEN_Msk | (0xe << Arasan_EMMC_Distributor_CONTROL1_DATA_TOUNIT_Pos); - - EMMC->IRPT_MASK = 0xffffffff; - - // Start clocking the card. - _set_card_clk(0, 400); - - sdmmc_err_t err = SDMMC_ERR_INVALID_RESPONSE; - size_t tries = 3; - while (err == SDMMC_ERR_INVALID_RESPONSE && tries > 0) { - err = sdmmc_card_init(&self->host_info, &self->card_info); - if (err != SDMMC_OK) { - mp_printf(&mp_plat_print, "SD card init failed %d\n", err); - } else if (tries < 3) { - mp_printf(&mp_plat_print, "SD card init success\n"); - } - tries--; - } - - self->init = err == SDMMC_OK; - - self->capacity = self->card_info.csd.capacity; - COMPLETE_MEMORY_READS; -} - -uint32_t common_hal_sdioio_sdcard_get_count(sdioio_sdcard_obj_t *self) { - return self->capacity; -} - -uint32_t common_hal_sdioio_sdcard_get_frequency(sdioio_sdcard_obj_t *self) { - return self->frequency; -} - -uint8_t common_hal_sdioio_sdcard_get_width(sdioio_sdcard_obj_t *self) { - return self->num_data; -} - -static void check_whole_block(mp_buffer_info_t *bufinfo) { - if (bufinfo->len % 512) { - mp_raise_ValueError(MP_ERROR_TEXT("Buffer length must be a multiple of 512")); - } -} - -// Native function for VFS blockdev layer -mp_negative_errno_t sdioio_sdcard_writeblocks(mp_obj_t self_in, uint8_t *buf, - uint32_t start_block, uint32_t num_blocks) { - sdioio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (!self->init) { - return -MP_EIO; - } - self->state_programming = true; - - COMPLETE_MEMORY_READS; - sdmmc_err_t error = sdmmc_write_sectors(&self->card_info, buf, - start_block, num_blocks); - COMPLETE_MEMORY_READS; - - if (error != SDMMC_OK) { - mp_printf(&mp_plat_print, "write sectors result %d\n", error); - return -MP_EIO; - } - - return 0; -} - -mp_negative_errno_t common_hal_sdioio_sdcard_writeblocks(sdioio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *bufinfo) { - if (!self->init) { - return -MP_EIO; - } - check_whole_block(bufinfo); - - uint32_t num_blocks = bufinfo->len / 512; - return sdioio_sdcard_writeblocks(MP_OBJ_FROM_PTR(self), bufinfo->buf, - start_block, num_blocks); -} - -// Native function for VFS blockdev layer -mp_negative_errno_t sdioio_sdcard_readblocks(mp_obj_t self_in, uint8_t *buf, - uint32_t start_block, uint32_t num_blocks) { - sdioio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (!self->init) { - return -MP_EIO; - } - COMPLETE_MEMORY_READS; - sdmmc_err_t error = sdmmc_read_sectors(&self->card_info, buf, - start_block, num_blocks); - COMPLETE_MEMORY_READS; - - if (error != SDMMC_OK) { - mp_printf(&mp_plat_print, "read sectors result %d when reading block %d for %d\n", error, start_block, num_blocks); - return -MP_EIO; - } - - return 0; -} - -mp_negative_errno_t common_hal_sdioio_sdcard_readblocks(sdioio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *bufinfo) { - if (!self->init) { - return -MP_EIO; - } - check_whole_block(bufinfo); - - uint32_t num_blocks = bufinfo->len / 512; - return sdioio_sdcard_readblocks(MP_OBJ_FROM_PTR(self), bufinfo->buf, - start_block, num_blocks); -} - -// Native function for VFS blockdev layer -bool sdioio_sdcard_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, - mp_int_t *out_value) { - sdioio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in); - *out_value = 0; - - switch (cmd) { - case MP_BLOCKDEV_IOCTL_DEINIT: - case MP_BLOCKDEV_IOCTL_SYNC: - // SDIO operations are synchronous, no action needed - return true; - - case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: - *out_value = common_hal_sdioio_sdcard_get_count(self); - return true; - - case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: - *out_value = 512; // SD cards use 512-byte sectors - return true; - - default: - return false; // Unsupported command - } -} - -bool common_hal_sdioio_sdcard_configure(sdioio_sdcard_obj_t *self, uint32_t frequency, uint8_t bits) { - if (!self->init) { - return false; - } - return true; -} - -bool common_hal_sdioio_sdcard_deinited(sdioio_sdcard_obj_t *self) { - return self->command_pin == COMMON_HAL_MCU_NO_PIN; -} - -void common_hal_sdioio_sdcard_deinit(sdioio_sdcard_obj_t *self) { - reset_pin_number(self->command_pin); - reset_pin_number(self->clock_pin); - reset_pin_number(self->data_pins[0]); - reset_pin_number(self->data_pins[1]); - reset_pin_number(self->data_pins[2]); - reset_pin_number(self->data_pins[3]); - - self->command_pin = COMMON_HAL_MCU_NO_PIN; - self->clock_pin = COMMON_HAL_MCU_NO_PIN; - self->data_pins[0] = COMMON_HAL_MCU_NO_PIN; - self->data_pins[1] = COMMON_HAL_MCU_NO_PIN; - self->data_pins[2] = COMMON_HAL_MCU_NO_PIN; - self->data_pins[3] = COMMON_HAL_MCU_NO_PIN; - self->init = false; -} - -void common_hal_sdioio_sdcard_never_reset(sdioio_sdcard_obj_t *self) { - never_reset_pin_number(self->command_pin); - never_reset_pin_number(self->clock_pin); - never_reset_pin_number(self->data_pins[0]); - never_reset_pin_number(self->data_pins[1]); - never_reset_pin_number(self->data_pins[2]); - never_reset_pin_number(self->data_pins[3]); -} diff --git a/ports/broadcom/common-hal/sdioio/SDCard.h b/ports/broadcom/common-hal/sdioio/SDCard.h deleted file mode 100644 index e63f8db99f2..00000000000 --- a/ports/broadcom/common-hal/sdioio/SDCard.h +++ /dev/null @@ -1,24 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2016 Scott Shawcroft -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "lib/sdmmc/include/sdmmc_types.h" - -typedef struct { - mp_obj_base_t base; - sdmmc_host_t host_info; - sdmmc_card_t card_info; - uint32_t frequency; - uint32_t capacity; - uint8_t num_data; - bool state_programming; - bool has_lock; - bool init; - uint8_t command_pin; - uint8_t clock_pin; - uint8_t data_pins[4]; -} sdioio_sdcard_obj_t; diff --git a/ports/broadcom/common-hal/sdioio/__init__.c b/ports/broadcom/common-hal/sdioio/__init__.c deleted file mode 100644 index 72d32ef2b2c..00000000000 --- a/ports/broadcom/common-hal/sdioio/__init__.c +++ /dev/null @@ -1,5 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT diff --git a/ports/broadcom/common-hal/videocore/Framebuffer.c b/ports/broadcom/common-hal/videocore/Framebuffer.c deleted file mode 100644 index 26672d95f4d..00000000000 --- a/ports/broadcom/common-hal/videocore/Framebuffer.c +++ /dev/null @@ -1,80 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - - -#include "bindings/videocore/Framebuffer.h" - -#include "py/gc.h" -#include "py/runtime.h" - -#include "peripherals/broadcom/caches.h" -#include "peripherals/broadcom/vcmailbox.h" - -void common_hal_videocore_framebuffer_construct(videocore_framebuffer_obj_t *self, - mp_uint_t width, mp_uint_t height) { - // These will be modified on success to indicate actual values. - uint32_t virtual_width = width; - uint32_t virtual_height = height; - uint32_t physical_width = width; - // TODO: Make the FB twice as tall if double buffering. - uint32_t physical_height = height; - uint32_t pitch = 0; - uint32_t bits_per_pixel = 0; - self->framebuffer = vcmailbox_get_framebuffer(&virtual_width, - &virtual_height, - &physical_width, - &physical_height, - &pitch, - &bits_per_pixel); - if (self->framebuffer == NULL) { - if (gc_alloc_possible()) { - mp_raise_ValueError(MP_ERROR_TEXT("no fb")); - } else { - mp_printf(&mp_plat_print, "no fb\n"); - } - self->width = 0; - self->height = 0; - return; - } - self->width = virtual_width; - self->height = virtual_height; - self->pitch = pitch; -} - -void common_hal_videocore_framebuffer_deinit(videocore_framebuffer_obj_t *self) { - if (vcmailbox_release_framebuffer()) { - self->framebuffer = NULL; - } - self->base.type = &mp_type_NoneType; -} - -bool common_hal_videocore_framebuffer_deinited(videocore_framebuffer_obj_t *self) { - return self->framebuffer == NULL; -} - -void common_hal_videocore_framebuffer_refresh(videocore_framebuffer_obj_t *self) { - data_clean(self->framebuffer, sizeof(uint32_t) * self->width * self->height); -} - -int common_hal_videocore_framebuffer_get_width(videocore_framebuffer_obj_t *self) { - return self->width; -} - -int common_hal_videocore_framebuffer_get_height(videocore_framebuffer_obj_t *self) { - return self->height; -} - -mp_int_t common_hal_videocore_framebuffer_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { - videocore_framebuffer_obj_t *self = (videocore_framebuffer_obj_t *)self_in; - bufinfo->buf = self->framebuffer; - bufinfo->typecode = 'L'; - bufinfo->len = self->pitch * self->height * sizeof(uint32_t); - return 0; -} - -int common_hal_videocore_framebuffer_get_row_stride(videocore_framebuffer_obj_t *self) { - return self->pitch; -} diff --git a/ports/broadcom/common-hal/videocore/Framebuffer.h b/ports/broadcom/common-hal/videocore/Framebuffer.h deleted file mode 100644 index 939204efbcd..00000000000 --- a/ports/broadcom/common-hal/videocore/Framebuffer.h +++ /dev/null @@ -1,18 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; - uint32_t *framebuffer; - mp_uint_t width; - mp_uint_t height; - uint32_t pitch; - bool doublebuffer; -} videocore_framebuffer_obj_t; diff --git a/ports/broadcom/config.txt b/ports/broadcom/config.txt deleted file mode 100644 index 1c12196b980..00000000000 --- a/ports/broadcom/config.txt +++ /dev/null @@ -1,10 +0,0 @@ - -# dtparam=audio=on -# dtoverlay=dwc2 -# core_freq_min=500 -gpio=22-27=np -enable_jtag_gpio=1 -# hdmi_group=1 -# hdmi_mode=16 -gpu_mem=16 -kernel_address=0x100000 diff --git a/ports/broadcom/firmware b/ports/broadcom/firmware deleted file mode 160000 index bf96d0eda59..00000000000 --- a/ports/broadcom/firmware +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bf96d0eda5952595d717fedb797aeb168483e9fa diff --git a/ports/broadcom/mpconfigport.h b/ports/broadcom/mpconfigport.h deleted file mode 100644 index 73fae8295de..00000000000 --- a/ports/broadcom/mpconfigport.h +++ /dev/null @@ -1,38 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -// Definitions that control circuitpy_mpconfig.h: - -//////////////////////////////////////////////////////////////////////////////////////////////////// - - -#define CIRCUITPY_MCU_FAMILY broadcom -#define MICROPY_PY_SYS_PLATFORM "BROADCOM" - -#if BCM_VERSION == 2837 || BCM_VERSION == 2711 -#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A) -#elif BCM_VERSION == 2835 -#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) -#endif -#define CIRCUITPY_DEFAULT_STACK_SIZE (0x10000) -#define CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE (1920) -#define CIRCUITPY_PROCESSOR_COUNT (4) - -#define MICROPY_FATFS_EXFAT (1) -#define MICROPY_FATFS_MKFS_FAT32 (1) - -#define CIRCUITPY_USB_DEVICE_HIGH_SPEED (1) - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// This also includes mpconfigboard.h. -#include "py/circuitpy_mpconfig.h" - -// Definitions that can be overridden by mpconfigboard.h: - -//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/ports/broadcom/mpconfigport.mk b/ports/broadcom/mpconfigport.mk deleted file mode 100644 index 844794b107a..00000000000 --- a/ports/broadcom/mpconfigport.mk +++ /dev/null @@ -1,32 +0,0 @@ -# All broadcom ports have longints. -LONGINT_IMPL = MPZ - -CIRCUITPY_FULL_BUILD = 1 - -# Modules in FULL_BUILD that we don't support. -CIRCUITPY_ANALOGIO = 0 -CIRCUITPY_AUDIOBUSIO = 0 -CIRCUITPY_AUDIOIO = 0 -CIRCUITPY_COUNTIO = 0 -CIRCUITPY_FREQUENCYIO = 0 -CIRCUITPY_I2CTARGET = 0 -CIRCUITPY_NVM = 0 -CIRCUITPY_PARALLELDISPLAYBUS = 0 -CIRCUITPY_PULSEIO = 0 -CIRCUITPY_PWMIO = 0 -CIRCUITPY_ROTARYIO = 0 - -CIRCUITPY_SDIOIO = 1 -CIRCUITPY_VIDEOCORE = 1 - -INTERNAL_FLASH_FILESYSTEM = 1 - -USB_NUM_ENDPOINT_PAIRS = 8 - -CIRCUITPY_BUILD_EXTENSIONS ?= disk.img.zip,kernel8.img - -ifeq ($(CHIP_VARIANT), "bcm2711") -CIRCUITPY_MIN_GCC_VERSION ?= 14 -else ifeq ($(CHIP_VARIANT), "bcm2837") -CIRCUITPY_MIN_GCC_VERSION ?= 14 -endif diff --git a/ports/broadcom/mphalport.c b/ports/broadcom/mphalport.c deleted file mode 100644 index 32718511f05..00000000000 --- a/ports/broadcom/mphalport.c +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -#include - -#include -#include "py/mpconfig.h" -#include "py/obj.h" -#include "shared-bindings/microcontroller/__init__.h" -#include "mphalport.h" - -#include "peripherals/broadcom/defines.h" - -void mp_hal_delay_us(mp_uint_t delay) { - uint32_t end = SYSTMR->CLO + delay; - // Wait if end is before current time because it must have wrapped. - while (end < SYSTMR->CLO) { - } - while (SYSTMR->CLO < end) { - } -} - -void mp_hal_disable_all_interrupts(void) { - common_hal_mcu_disable_interrupts(); -} - -void mp_hal_enable_all_interrupts(void) { - common_hal_mcu_enable_interrupts(); -} - -mp_uint_t cpu_get_regs_and_sp(mp_uint_t *regs) { - size_t sp = 0; - #if defined(__ARM_ARCH) && (__ARM_ARCH >= 8) - __asm__ ("mov %[out], sp" : [out] "=r" (sp)); - __asm__ ("mov %[out], x19" : [out] "=r" (regs[0])); - __asm__ ("mov %[out], x20" : [out] "=r" (regs[1])); - __asm__ ("mov %[out], x21" : [out] "=r" (regs[2])); - __asm__ ("mov %[out], x22" : [out] "=r" (regs[3])); - __asm__ ("mov %[out], x23" : [out] "=r" (regs[4])); - __asm__ ("mov %[out], x24" : [out] "=r" (regs[5])); - __asm__ ("mov %[out], x25" : [out] "=r" (regs[6])); - __asm__ ("mov %[out], x26" : [out] "=r" (regs[7])); - __asm__ ("mov %[out], x27" : [out] "=r" (regs[8])); - __asm__ ("mov %[out], x28" : [out] "=r" (regs[9])); - #else - __asm__ ("mov %[out], sp" : [out] "=r" (sp)); - __asm__ ("mov %[out], r4" : [out] "=r" (regs[0])); - __asm__ ("mov %[out], r5" : [out] "=r" (regs[1])); - __asm__ ("mov %[out], r6" : [out] "=r" (regs[2])); - __asm__ ("mov %[out], r7" : [out] "=r" (regs[3])); - __asm__ ("mov %[out], r8" : [out] "=r" (regs[4])); - __asm__ ("mov %[out], r9" : [out] "=r" (regs[5])); - __asm__ ("mov %[out], r10" : [out] "=r" (regs[6])); - __asm__ ("mov %[out], r11" : [out] "=r" (regs[7])); - __asm__ ("mov %[out], r12" : [out] "=r" (regs[8])); - __asm__ ("mov %[out], r13" : [out] "=r" (regs[9])); - #endif - - return sp; -} diff --git a/ports/broadcom/mphalport.h b/ports/broadcom/mphalport.h deleted file mode 100644 index d42915aa8bd..00000000000 --- a/ports/broadcom/mphalport.h +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include -#include "py/obj.h" - - -#include "supervisor/shared/tick.h" - -void mp_hal_delay_ms(mp_uint_t ms); -void mp_hal_delay_us(mp_uint_t us); - -#define mp_hal_ticks_ms() ((mp_uint_t)supervisor_ticks_ms32()) - -void mp_hal_set_interrupt_char(int c); -int mp_hal_stdin_rx_chr(void); -mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len); - -#ifdef MICROPY_HW_USBHOST -#include "usbkbd.h" - -void usbkbd_setup(); -#endif diff --git a/ports/broadcom/peripherals b/ports/broadcom/peripherals deleted file mode 160000 index ec1043800fc..00000000000 --- a/ports/broadcom/peripherals +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ec1043800fc987f7067fc07615193439843695d6 diff --git a/ports/broadcom/qstrdefsport.h b/ports/broadcom/qstrdefsport.h deleted file mode 100644 index 79d7f33276e..00000000000 --- a/ports/broadcom/qstrdefsport.h +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2014 Damien P. George -// -// SPDX-License-Identifier: MIT - -// Do not use #pragma once in this file; it will cause a warning during qstr generation. - -// qstrs specific to this port, only needed if they aren't auto-generated - -// Prevent uncrustify from modifying these lines. -// *FORMAT-OFF* - -// Entries for sys.path -Q(/sd) -Q(/sd/lib) diff --git a/ports/broadcom/supervisor/internal_flash.c b/ports/broadcom/supervisor/internal_flash.c deleted file mode 100644 index 2e75154ea35..00000000000 --- a/ports/broadcom/supervisor/internal_flash.c +++ /dev/null @@ -1,160 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/flash.h" -#include "supervisor/internal_flash.h" - -#include - -#include "shared-bindings/digitalio/DigitalInOut.h" -#include "shared-bindings/sdioio/SDCard.h" - -static sdioio_sdcard_obj_t sd; - -static uint32_t first_lba = 0; -static uint32_t sector_count = 0; - -static bool inited = false; - -void supervisor_flash_init(void) { - if (inited) { - return; - } - inited = true; - - #if BCM_VERSION != 2711 - const mcu_pin_obj_t *data_pins[4] = { - &pin_GPIO50, - &pin_GPIO51, - &pin_GPIO52, - &pin_GPIO53, - }; - common_hal_sdioio_sdcard_construct(&sd, - &pin_GPIO48, &pin_GPIO49, - 4, data_pins, 8000000); - #else - common_hal_sdioio_sdcard_construct(&sd, - NULL, NULL, - 0, NULL, 8000000); - #endif - common_hal_sdioio_sdcard_never_reset(&sd); - - uint32_t buffer[512 / sizeof(uint32_t)]; - mp_buffer_info_t bufinfo; - bufinfo.len = 512; - bufinfo.buf = buffer; - common_hal_sdioio_sdcard_readblocks(&sd, 0, &bufinfo); - size_t partition_count = 0; - size_t first_free_sector = 0; - for (size_t i = 0; i < 4; i++) { - uint32_t part_first_lba = 0; - uint32_t part_sector_count = 0; - for (size_t j = 0; j < 16; j++) { - size_t offset = 446 + i * 16 + j; - uint8_t value = ((uint8_t *)buffer)[offset]; - // Little endian - if (j >= 8 && j < 12) { - part_first_lba |= value << (8 * (j - 8)); - } else if (j >= 12) { - part_sector_count |= value << (8 * (j - 12)); - } - } - if (part_sector_count > 0) { - partition_count += 1; - first_free_sector = part_first_lba + part_sector_count; - } - // If the second partition exists, then use it (possibly after reformatting it.) - if (i == 1 && part_sector_count > 0 && part_first_lba > 0) { - sector_count = part_sector_count; - first_lba = part_first_lba; - } else if (i > 1 && part_sector_count > 0) { - sector_count = 0; - } - } - - // Special case where most of the card is unused and the first and only - // partition is likely the CP boot partition. (It is 255MB starting at 1MB) - size_t sectors_per_mb = 2048; - if (partition_count == 1 && first_free_sector == 256 * sectors_per_mb) { - size_t total_sectors = common_hal_sdioio_sdcard_get_count(&sd); - // Align to a MB - total_sectors = total_sectors - (total_sectors % sectors_per_mb); - // Don't use the last MB because the imager erases it. GPT tables put a - // backup there sometimes. - total_sectors -= sectors_per_mb; - size_t new_sector_count = total_sectors - first_free_sector; - - // Modify the MBR - size_t offset = 446 + 1 * 16; - uint8_t *part = ((uint8_t *)buffer) + offset; - part[0] = 0; // not boot - part[1] = 0xff; // Old unused CHS sizing - part[2] = 0xff; - part[3] = 0xff; - uint8_t type = 0x0C; // FAT32 with LBA - if (new_sector_count >= 0x4000000) { - type = 0x07; // Exfat when 32GB+ - } - part[4] = type; - part[5] = 0xff; // Old unused CHS sizing - part[6] = 0xff; - part[7] = 0xff; - part[8] = first_free_sector & 0xff; - part[9] = (first_free_sector >> 8) & 0xff; - part[10] = (first_free_sector >> 16) & 0xff; - part[11] = (first_free_sector >> 24) & 0xff; - part[12] = new_sector_count & 0xff; - part[13] = (new_sector_count >> 8) & 0xff; - part[14] = (new_sector_count >> 16) & 0xff; - part[15] = (new_sector_count >> 24) & 0xff; - - // Write back the modified MBR with the new partition. CircuitPython can - // detect a previously formatted FAT filesystem later if only the MBR and - // BOOT partition was updated. - if (common_hal_sdioio_sdcard_writeblocks(&sd, 0, &bufinfo) == 0) { - sector_count = new_sector_count; - first_lba = first_free_sector; - } - } -} - -uint32_t supervisor_flash_get_block_size(void) { - return 512; -} - -uint32_t supervisor_flash_get_block_count(void) { - return sector_count; -} - -void port_internal_flash_flush(void) { -} - -mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) { - mp_buffer_info_t bufinfo; - bufinfo.len = 512; - for (size_t i = 0; i < num_blocks; i++) { - bufinfo.buf = dest + 512 * i; - if (common_hal_sdioio_sdcard_readblocks(&sd, first_lba + block + i, &bufinfo) != 0) { - return 1; // error - } - } - return 0; // success -} - -mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t block, uint32_t num_blocks) { - mp_buffer_info_t bufinfo; - bufinfo.len = 512; - for (size_t i = 0; i < num_blocks; i++) { - bufinfo.buf = (uint8_t *)src + 512 * i; - if (common_hal_sdioio_sdcard_writeblocks(&sd, first_lba + block + i, &bufinfo) != 0) { - return 1; // error - } - } - return 0; // success -} - -void supervisor_flash_release_cache(void) { -} diff --git a/ports/broadcom/supervisor/internal_flash.h b/ports/broadcom/supervisor/internal_flash.h deleted file mode 100644 index 94b1f0bc973..00000000000 --- a/ports/broadcom/supervisor/internal_flash.h +++ /dev/null @@ -1,10 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT -#pragma once - -#include - -#include "mpconfigport.h" diff --git a/ports/broadcom/supervisor/port.c b/ports/broadcom/supervisor/port.c deleted file mode 100644 index a0f0be30dd7..00000000000 --- a/ports/broadcom/supervisor/port.c +++ /dev/null @@ -1,179 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include -#include - -#include "supervisor/board.h" -#include "supervisor/port.h" - -#include "genhdr/mpversion.h" - -#include "common-hal/rtc/RTC.h" -#include "common-hal/busio/I2C.h" -#include "common-hal/busio/SPI.h" -#include "common-hal/busio/UART.h" - -#include "peripherals/broadcom/caches.h" -#include "peripherals/broadcom/cpu.h" -#include "peripherals/broadcom/defines.h" -#include "peripherals/broadcom/interrupts.h" -#include "peripherals/broadcom/mmu.h" - -#include "shared-bindings/microcontroller/__init__.h" - -#include "supervisor/shared/safe_mode.h" -#include "supervisor/shared/stack.h" -#include "supervisor/shared/tick.h" - -#include "peripherals/broadcom/vcmailbox.h" - -// #include "tusb.h" - -extern volatile bool mp_msc_enabled; - -static uint32_t board_revision; - -safe_mode_t port_init(void) { - board_revision = vcmailbox_get_board_revision(); - setup_mmu_flat_map(); - - init_caches(); - - // Set brown out. - - // Reset everything into a known state before board_init. - reset_port(); - - // Initialize RTC - // common_hal_rtc_init(); - - // For the tick. - // hardware_alarm_claim(0); - // hardware_alarm_set_callback(0, _tick_callback); - - // Check brownout. - - if (board_requests_safe_mode()) { - return SAFE_MODE_USER; - } - - return SAFE_MODE_NONE; -} - -void reset_port(void) { - #if CIRCUITPY_BUSIO - reset_i2c(); - reset_uart(); - #endif - - #if CIRCUITPY_PWMIO - pwmout_reset(); - #endif - - #if CIRCUITPY_RTC - rtc_reset(); - #endif - - #if CIRCUITPY_AUDIOPWMIO - audiopwmout_reset(); - #endif - #if CIRCUITPY_AUDIOCORE - audio_dma_reset(); - #endif -} - -void reset_to_bootloader(void) { - // reset_usb_boot(0, 0); - while (true) { - } -} - -void reset_cpu(void) { - // Don't actually reset because we can't store the safe mode info. - // PM->WDOG = 1 << PM_WDOG_TIME_Pos | PM_WDOG_PASSWD_PASSWD << PM_WDOG_PASSWD_Pos; - // PM->RSTC = PM_RSTC_WRCFG_FULL_RESET << PM_RSTC_WRCFG_Pos | PM_RSTC_PASSWD_PASSWD << PM_RSTC_PASSWD_Pos; - while (true) { - } -} - -// From the linker script -extern uint32_t __bss_end; -extern uint32_t _ld_ram_end; -uint32_t *port_stack_get_limit(void) { - return (uint32_t *)0x4; -} - -uint32_t *port_stack_get_top(void) { - return (uint32_t *)0x100000; -} - -uint32_t *port_heap_get_bottom(void) { - return &__bss_end; -} - -uint32_t *port_heap_get_top(void) { - // 32M heap - return (uint32_t *)(((size_t)&__bss_end) + 32 * 1024 * 1024); -} - -void port_set_saved_word(uint32_t value) { - // NOTE: This doesn't survive pressing the reset button (aka toggling RUN). - __bss_end = value; - data_clean_and_invalidate(&__bss_end, sizeof(uint32_t)); -} - -uint32_t port_get_saved_word(void) { - return __bss_end; -} - -uint64_t port_get_raw_ticks(uint8_t *subticks) { - COMPLETE_MEMORY_READS; - uint64_t hi = SYSTMR->CHI; - uint64_t lo = SYSTMR->CLO; - if (hi != SYSTMR->CHI) { - hi = SYSTMR->CHI; - lo = SYSTMR->CLO; - } - COMPLETE_MEMORY_READS; - uint64_t microseconds = hi << 32 | lo; - int64_t all_subticks = microseconds * 512 / 15625; - if (subticks != NULL) { - *subticks = all_subticks % 32; - } - return all_subticks / 32; -} - -void TIMER_1_IRQHandler(void) { - SYSTMR->C1 += 977; - SYSTMR->CS_b.M1 = 1; - supervisor_tick(); -} - -// Enable 1/1024 second tick. -void port_enable_tick(void) { - COMPLETE_MEMORY_READS; - SYSTMR->CS_b.M1 = 1; - SYSTMR->C1 = SYSTMR->CLO + 977; - BP_EnableIRQ(TIMER_1_IRQn); -} - -// Disable 1/1024 second tick. -void port_disable_tick(void) { - BP_DisableIRQ(TIMER_1_IRQn); -} - -void port_interrupt_after_ticks(uint32_t ticks) { -} - -void port_idle_until_interrupt(void) { - common_hal_mcu_disable_interrupts(); -// if (!tud_task_event_ready()) { -// // asm volatile ("dsb 0xF":::"memory"); -// // __wfi(); -// } - common_hal_mcu_enable_interrupts(); -} diff --git a/ports/broadcom/supervisor/usb.c b/ports/broadcom/supervisor/usb.c deleted file mode 100644 index 166ed4e3e2e..00000000000 --- a/ports/broadcom/supervisor/usb.c +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017, 2018 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "lib/tinyusb/src/device/usbd.h" -#include "py/runtime.h" -#include "supervisor/background_callback.h" -#include "supervisor/usb.h" - -#include "peripherals/broadcom/interrupts.h" -#include "peripherals/broadcom/vcmailbox.h" - -uint32_t SystemCoreClock = 700 * 1000 * 1000; - -void USB_IRQHandler(void) { - usb_irq_handler(0); -} - -void init_usb_hardware(void) { - vcmailbox_set_power_state(VCMAILBOX_DEVICE_USB_HCD, true); - - BP_SetPriority(USB_IRQn, 0x00); - BP_ClearPendingIRQ(USB_IRQn); - BP_EnableIRQ(USB_IRQn); - BP_EnableIRQs(); -} diff --git a/ports/cxd56/common-hal/analogio/AnalogIn.c b/ports/cxd56/common-hal/analogio/AnalogIn.c index b124f1bac80..94e149d0722 100644 --- a/ports/cxd56/common-hal/analogio/AnalogIn.c +++ b/ports/cxd56/common-hal/analogio/AnalogIn.c @@ -108,14 +108,3 @@ float common_hal_analogio_analogin_get_reference_voltage(analogio_analogin_obj_t return voltage; } - -void analogin_reset(void) { - for (int i = 0; i < MP_ARRAY_SIZE(analogin_dev); i++) { - if (analogin_dev[i].fd >= 0) { - // stop ADC - ioctl(analogin_dev[i].fd, ANIOC_CXD56_STOP, 0); - close(analogin_dev[i].fd); - analogin_dev[i].fd = -1; - } - } -} diff --git a/ports/cxd56/common-hal/analogio/AnalogIn.h b/ports/cxd56/common-hal/analogio/AnalogIn.h index affc7f805e8..da36b6818ff 100644 --- a/ports/cxd56/common-hal/analogio/AnalogIn.h +++ b/ports/cxd56/common-hal/analogio/AnalogIn.h @@ -15,5 +15,3 @@ typedef struct { const mcu_pin_obj_t *pin; int8_t number; } analogio_analogin_obj_t; - -void analogin_reset(void); diff --git a/ports/cxd56/common-hal/busio/I2C.c b/ports/cxd56/common-hal/busio/I2C.c index 8e846b2402d..a7e5b9dc608 100644 --- a/ports/cxd56/common-hal/busio/I2C.c +++ b/ports/cxd56/common-hal/busio/I2C.c @@ -127,8 +127,3 @@ mp_negative_errno_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint1 return common_hal_busio_i2c_read(self, addr, in_data, in_len); } - -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - never_reset_pin_number(self->scl_pin->number); - never_reset_pin_number(self->sda_pin->number); -} diff --git a/ports/cxd56/common-hal/busio/SPI.c b/ports/cxd56/common-hal/busio/SPI.c index 595b868f348..096dc1ba03e 100644 --- a/ports/cxd56/common-hal/busio/SPI.c +++ b/ports/cxd56/common-hal/busio/SPI.c @@ -168,9 +168,3 @@ uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self) { uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) { return self->polarity; } - -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { - never_reset_pin_number(self->clock_pin->number); - never_reset_pin_number(self->mosi_pin->number); - never_reset_pin_number(self->miso_pin->number); -} diff --git a/ports/cxd56/common-hal/digitalio/DigitalInOut.c b/ports/cxd56/common-hal/digitalio/DigitalInOut.c index 6936fe4f8cd..855350708b6 100644 --- a/ports/cxd56/common-hal/digitalio/DigitalInOut.c +++ b/ports/cxd56/common-hal/digitalio/DigitalInOut.c @@ -115,7 +115,3 @@ digitalinout_result_t common_hal_digitalio_digitalinout_set_pull(digitalio_digit digitalio_pull_t common_hal_digitalio_digitalinout_get_pull(digitalio_digitalinout_obj_t *self) { return self->pull; } - -void common_hal_digitalio_digitalinout_never_reset(digitalio_digitalinout_obj_t *self) { - never_reset_pin_number(self->pin->number); -} diff --git a/ports/cxd56/common-hal/microcontroller/Pin.c b/ports/cxd56/common-hal/microcontroller/Pin.c index 0f9d6b1f3bd..9ffb04e774b 100644 --- a/ports/cxd56/common-hal/microcontroller/Pin.c +++ b/ports/cxd56/common-hal/microcontroller/Pin.c @@ -15,7 +15,6 @@ typedef struct { const mcu_pin_obj_t *pin; - bool reset; bool free; } pin_status_t; @@ -66,39 +65,39 @@ const mcu_pin_obj_t pin_HPADC0 = PIN(4, true); const mcu_pin_obj_t pin_HPADC1 = PIN(5, true); static pin_status_t pins[] = { - { &pin_UART2_RXD, true, true }, - { &pin_UART2_TXD, true, true }, - { &pin_HIF_IRQ_OUT, true, true }, - { &pin_PWM3, true, true }, - { &pin_SPI2_MOSI, true, true }, - { &pin_PWM1, true, true }, - { &pin_PWM0, true, true }, - { &pin_SPI3_CS1_X, true, true }, - { &pin_SPI2_MISO, true, true }, - { &pin_PWM2, true, true }, - { &pin_SPI4_CS_X, true, true }, - { &pin_SPI4_MOSI, true, true }, - { &pin_SPI4_MISO, true, true }, - { &pin_SPI4_SCK, true, true }, - { &pin_I2C0_BDT, true, true }, - { &pin_I2C0_BCK, true, true }, - { &pin_EMMC_DATA0, true, true }, - { &pin_EMMC_DATA1, true, true }, - { &pin_I2S0_DATA_OUT, true, true }, - { &pin_I2S0_DATA_IN, true, true }, - { &pin_EMMC_DATA2, true, true }, - { &pin_EMMC_DATA3, true, true }, - { &pin_SEN_IRQ_IN, true, true }, - { &pin_EMMC_CLK, true, true }, - { &pin_EMMC_CMD, true, true }, - { &pin_I2S0_LRCK, true, true }, - { &pin_I2S0_BCK, true, true }, - { &pin_UART2_CTS, true, true }, - { &pin_UART2_RTS, true, true }, - { &pin_I2S1_BCK, true, true }, - { &pin_I2S1_LRCK, true, true }, - { &pin_I2S1_DATA_IN, true, true }, - { &pin_I2S1_DATA_OUT, true, true }, + { &pin_UART2_RXD, true }, + { &pin_UART2_TXD, true }, + { &pin_HIF_IRQ_OUT, true }, + { &pin_PWM3, true }, + { &pin_SPI2_MOSI, true }, + { &pin_PWM1, true }, + { &pin_PWM0, true }, + { &pin_SPI3_CS1_X, true }, + { &pin_SPI2_MISO, true }, + { &pin_PWM2, true }, + { &pin_SPI4_CS_X, true }, + { &pin_SPI4_MOSI, true }, + { &pin_SPI4_MISO, true }, + { &pin_SPI4_SCK, true }, + { &pin_I2C0_BDT, true }, + { &pin_I2C0_BCK, true }, + { &pin_EMMC_DATA0, true }, + { &pin_EMMC_DATA1, true }, + { &pin_I2S0_DATA_OUT, true }, + { &pin_I2S0_DATA_IN, true }, + { &pin_EMMC_DATA2, true }, + { &pin_EMMC_DATA3, true }, + { &pin_SEN_IRQ_IN, true }, + { &pin_EMMC_CLK, true }, + { &pin_EMMC_CMD, true }, + { &pin_I2S0_LRCK, true }, + { &pin_I2S0_BCK, true }, + { &pin_UART2_CTS, true }, + { &pin_UART2_RTS, true }, + { &pin_I2S1_BCK, true }, + { &pin_I2S1_LRCK, true }, + { &pin_I2S1_DATA_IN, true }, + { &pin_I2S1_DATA_OUT, true }, }; bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { @@ -111,14 +110,6 @@ bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { return true; } -void never_reset_pin_number(uint8_t pin_number) { - for (int i = 0; i < MP_ARRAY_SIZE(pins); i++) { - if (pins[i].pin->number == pin_number) { - pins[i].reset = false; - } - } -} - void reset_pin_number(uint8_t pin_number) { for (int i = 0; i < MP_ARRAY_SIZE(pins); i++) { if (pins[i].pin->number == pin_number) { @@ -127,17 +118,6 @@ void reset_pin_number(uint8_t pin_number) { } } -void reset_all_pins(void) { - for (int i = 0; i < MP_ARRAY_SIZE(pins); i++) { - if (!pins[i].free && pins[i].reset) { - board_gpio_write(pins[i].pin->number, -1); - board_gpio_config(pins[i].pin->number, 0, false, true, PIN_FLOAT); - board_gpio_int(pins[i].pin->number, false); - board_gpio_intconfig(pins[i].pin->number, 0, false, NULL); - pins[i].free = true; - } - } -} void claim_pin(const mcu_pin_obj_t *pin) { for (int i = 0; i < MP_ARRAY_SIZE(pins); i++) { diff --git a/ports/cxd56/common-hal/microcontroller/Pin.h b/ports/cxd56/common-hal/microcontroller/Pin.h index 8c27abe76f2..94c01d2b7ad 100644 --- a/ports/cxd56/common-hal/microcontroller/Pin.h +++ b/ports/cxd56/common-hal/microcontroller/Pin.h @@ -69,7 +69,5 @@ extern const mcu_pin_obj_t pin_LPADC3; extern const mcu_pin_obj_t pin_HPADC0; extern const mcu_pin_obj_t pin_HPADC1; -void never_reset_pin_number(uint8_t pin_number); void reset_pin_number(uint8_t pin_number); -void reset_all_pins(void); void claim_pin(const mcu_pin_obj_t *pin); diff --git a/ports/cxd56/common-hal/pwmio/PWMOut.c b/ports/cxd56/common-hal/pwmio/PWMOut.c index f754858f306..7ea96f4d4f2 100644 --- a/ports/cxd56/common-hal/pwmio/PWMOut.c +++ b/ports/cxd56/common-hal/pwmio/PWMOut.c @@ -16,14 +16,13 @@ typedef struct { const char *devpath; const mcu_pin_obj_t *pin; int fd; - bool reset; } pwmout_dev_t; static pwmout_dev_t pwmout_dev[] = { - {"/dev/pwm0", &pin_PWM0, -1, true}, - {"/dev/pwm1", &pin_PWM1, -1, true}, - {"/dev/pwm2", &pin_PWM2, -1, true}, - {"/dev/pwm3", &pin_PWM3, -1, true} + {"/dev/pwm0", &pin_PWM0, -1}, + {"/dev/pwm1", &pin_PWM1, -1}, + {"/dev/pwm2", &pin_PWM2, -1}, + {"/dev/pwm3", &pin_PWM3, -1} }; pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self, @@ -70,8 +69,6 @@ void common_hal_pwmio_pwmout_deinit(pwmio_pwmout_obj_t *self) { return; } - pwmout_dev[self->number].reset = true; - ioctl(pwmout_dev[self->number].fd, PWMIOC_STOP, 0); close(pwmout_dev[self->number].fd); pwmout_dev[self->number].fd = -1; @@ -110,12 +107,6 @@ bool common_hal_pwmio_pwmout_get_variable_frequency(pwmio_pwmout_obj_t *self) { return self->variable_frequency; } -void common_hal_pwmio_pwmout_never_reset(pwmio_pwmout_obj_t *self) { - never_reset_pin_number(self->pin->number); - - pwmout_dev[self->number].reset = false; -} - void pwmout_start(uint8_t pwm_num) { ioctl(pwmout_dev[pwm_num].fd, PWMIOC_START, 0); } diff --git a/ports/cxd56/common-hal/sdioio/SDCard.c b/ports/cxd56/common-hal/sdioio/SDCard.c index 227011a8871..4e48e0e0a2a 100644 --- a/ports/cxd56/common-hal/sdioio/SDCard.c +++ b/ports/cxd56/common-hal/sdioio/SDCard.c @@ -164,11 +164,3 @@ bool sdioio_sdcard_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, return false; // Unsupported command } } - -void common_hal_sdioio_sdcard_never_reset(sdioio_sdcard_obj_t *self) { - never_reset_pin_number(self->clock_pin->number); - never_reset_pin_number(self->command_pin->number); - for (uint8_t i = 0; i < DATA_PINS_NUM; i++) { - never_reset_pin_number(self->data_pins[i]->number); - } -} diff --git a/ports/cxd56/mpconfigport.mk b/ports/cxd56/mpconfigport.mk index ebfaa91f251..75ab534d7fb 100644 --- a/ports/cxd56/mpconfigport.mk +++ b/ports/cxd56/mpconfigport.mk @@ -25,3 +25,6 @@ CIRCUITPY_USB_MIDI = 0 INTERNAL_LIBM = 1 CIRCUITPY_BUILD_EXTENSIONS ?= spk + +# Cleanup is performed by GC finalizers (__del__ -> deinit) instead of a bulk pin reset. +CIRCUITPY_BULK_RESET = 0 diff --git a/ports/cxd56/supervisor/port.c b/ports/cxd56/supervisor/port.c index 3dc54df96fb..d05c3aba1b3 100644 --- a/ports/cxd56/supervisor/port.c +++ b/ports/cxd56/supervisor/port.c @@ -57,9 +57,6 @@ void reset_cpu(void) { } void reset_port(void) { - #if CIRCUITPY_ANALOGIO - analogin_reset(); - #endif #if CIRCUITPY_BUSIO busio_uart_reset(); #endif diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index b772e384410..881bfbf45e6 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -872,7 +872,12 @@ ifneq ($(CIRCUITPY_BLEIO_NATIVE),0) BLE_SDKCONFIG := ;esp-idf-config/sdkconfig-ble.defaults endif -SDKCONFIGS := esp-idf-config/sdkconfig.defaults;$(DEBUG_SDKCONFIG);$(FLASH_SIZE_SDKCONFIG);$(FLASH_MODE_SDKCONFIG);$(FLASH_SPEED_SDKCONFIG);$(PSRAM_SDKCONFIG);$(PSRAM_SIZE_SDKCONFIG);$(PSRAM_MODE_SDKCONFIG);$(PSRAM_SPEED_SDKCONFIG);$(BLE_SDKCONFIG);$(TARGET_SDKCONFIG);boards/$(BOARD)/sdkconfig +# Loaded native code executes from RAM, which memory protection forbids. +ifneq (,$(filter 1,$(CIRCUITPY_ENABLE_MPY_NATIVE) $(CIRCUITPY_LOAD_NATIVE))) + NATIVE_SDKCONFIG := ;esp-idf-config/sdkconfig-native.defaults +endif + +SDKCONFIGS := esp-idf-config/sdkconfig.defaults;$(DEBUG_SDKCONFIG);$(FLASH_SIZE_SDKCONFIG);$(FLASH_MODE_SDKCONFIG);$(FLASH_SPEED_SDKCONFIG);$(PSRAM_SDKCONFIG);$(PSRAM_SIZE_SDKCONFIG);$(PSRAM_MODE_SDKCONFIG);$(PSRAM_SPEED_SDKCONFIG);$(BLE_SDKCONFIG)$(NATIVE_SDKCONFIG);$(TARGET_SDKCONFIG);boards/$(BOARD)/sdkconfig # create the config headers .PHONY: do-sdkconfig @@ -884,6 +889,8 @@ $(BUILD)/esp-idf/config/sdkconfig.h: boards/$(BOARD)/sdkconfig boards/$(BOARD)/m $(Q)$(PYTHON) tools/check-sdkconfig.py \ CIRCUITPY_DUALBANK=$(CIRCUITPY_DUALBANK) \ CIRCUITPY_STORAGE_EXTEND=$(CIRCUITPY_STORAGE_EXTEND) \ + CIRCUITPY_ENABLE_MPY_NATIVE=$(CIRCUITPY_ENABLE_MPY_NATIVE) \ + CIRCUITPY_LOAD_NATIVE=$(CIRCUITPY_LOAD_NATIVE) \ $@ # build a lib diff --git a/ports/espressif/bindings/espcamera/Camera.c b/ports/espressif/bindings/espcamera/Camera.c index 9a74824a053..a19e8891ea5 100644 --- a/ports/espressif/bindings/espcamera/Camera.c +++ b/ports/espressif/bindings/espcamera/Camera.c @@ -95,9 +95,9 @@ static mp_obj_t espcamera_camera_make_new(const mp_obj_type_t *type, size_t n_ar MP_STATIC_ASSERT(MP_ARRAY_SIZE(allowed_args) == NUM_ARGS); mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - uint8_t data_pins[8]; + const mcu_pin_obj_t *data_pins[8]; uint8_t data_pin_count; - validate_pins(MP_QSTR_data_pins, data_pins, MP_ARRAY_SIZE(data_pins), args[ARG_data_pins].u_obj, &data_pin_count); + validate_list_is_free_pins(MP_QSTR_data_pins, data_pins, MP_ARRAY_SIZE(data_pins), args[ARG_data_pins].u_obj, &data_pin_count); mp_arg_validate_length(data_pin_count, 8, MP_QSTR_data_pins); const mcu_pin_obj_t *pixel_clock_pin = diff --git a/ports/espressif/bindings/espcamera/Camera.h b/ports/espressif/bindings/espcamera/Camera.h index 7ab563713f3..10c97d584ce 100644 --- a/ports/espressif/bindings/espcamera/Camera.h +++ b/ports/espressif/bindings/espcamera/Camera.h @@ -18,7 +18,7 @@ typedef struct espcamera_camera_obj espcamera_camera_obj_t; extern void common_hal_espcamera_camera_construct( espcamera_camera_obj_t *self, - uint8_t data_pins[8], + const mcu_pin_obj_t **data_pins, const mcu_pin_obj_t *external_clock_pin, const mcu_pin_obj_t *pixel_clock_pin, const mcu_pin_obj_t *vsync_pin, diff --git a/ports/espressif/boards/elio_eliobot_s3/board.c b/ports/espressif/boards/elio_eliobot_s3/board.c new file mode 100644 index 00000000000..895f4397351 --- /dev/null +++ b/ports/espressif/boards/elio_eliobot_s3/board.c @@ -0,0 +1,39 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Romain Boutrois +// +// SPDX-License-Identifier: MIT + +#include "supervisor/board.h" +#include "mpconfigboard.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "py/mpstate.h" +#include "supervisor/filesystem.h" + +void board_init(void) { + mp_import_stat_t stat_b = mp_import_stat("boot.py"); + if (stat_b != MP_IMPORT_STAT_FILE) { + supervisor_vfs_t *fs_mount = filesystem_circuitpy(); + supervisor_vfs_file_t fs; + // Default boot.py: CIRCUITPY is read-only over USB so children can't + // break the robot's files from the drive, while code sent over the + // serial REPL or BLE (Eliobot's web editor) can still write them. + const byte buffer[] = + "import storage\n" + "\n" + "# Eliobot default: the USB drive is read-only, and code sent by\n" + "# the Elioblocs editor can write files. To edit files from the drive\n" + "# instead, comment out the next line.\n" + "storage.remount(\"/\", False)\n"; + if (supervisor_vfs_open_file(fs_mount, "/boot.py", + SUPERVISOR_FS_OPEN_WRITE | SUPERVISOR_FS_OPEN_CREATE | + SUPERVISOR_FS_OPEN_TRUNCATE, 0, &fs) == SUPERVISOR_FS_OK) { + size_t char_written = 0; + supervisor_vfs_write_file(&fs, buffer, sizeof(buffer) - 1, &char_written); + supervisor_vfs_close_file(&fs); + } + } +} + +// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/espressif/boards/elio_eliobot_s3/mpconfigboard.h b/ports/espressif/boards/elio_eliobot_s3/mpconfigboard.h new file mode 100644 index 00000000000..7919df48932 --- /dev/null +++ b/ports/espressif/boards/elio_eliobot_s3/mpconfigboard.h @@ -0,0 +1,27 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Romain Boutrois +// +// SPDX-License-Identifier: MIT + +#pragma once + +// Micropython setup + +#define MICROPY_HW_BOARD_NAME "Eliobot" +#define MICROPY_HW_MCU_NAME "ESP32S3" +#define CIRCUITPY_DRIVE_LABEL "ELIOBOT" + +// Current boards have one status NeoPixel; the next revision chains eight. +#define MICROPY_HW_NEOPIXEL (&pin_GPIO1) +#define MICROPY_HW_NEOPIXEL_COUNT (8) + +#define DEFAULT_I2C_BUS_SCL (&pin_GPIO9) +#define DEFAULT_I2C_BUS_SDA (&pin_GPIO8) + +#define DEFAULT_SPI_BUS_SCK (&pin_GPIO39) +#define DEFAULT_SPI_BUS_MOSI (&pin_GPIO40) +#define DEFAULT_SPI_BUS_MISO (&pin_GPIO41) + +#define DEFAULT_UART_BUS_RX (&pin_GPIO44) +#define DEFAULT_UART_BUS_TX (&pin_GPIO43) diff --git a/ports/espressif/boards/elio_eliobot_s3/mpconfigboard.mk b/ports/espressif/boards/elio_eliobot_s3/mpconfigboard.mk new file mode 100644 index 00000000000..405b8e872df --- /dev/null +++ b/ports/espressif/boards/elio_eliobot_s3/mpconfigboard.mk @@ -0,0 +1,27 @@ +USB_VID = 0x303a +USB_PID = 0x8384 +USB_PRODUCT = "Eliobot" +USB_MANUFACTURER = "ELIO" +USB_INTERFACE_NAME = "Eliobot" + +IDF_TARGET = esp32s3 + +CIRCUITPY_ESP_FLASH_MODE = qio +CIRCUITPY_ESP_FLASH_FREQ = 80m +CIRCUITPY_ESP_FLASH_SIZE = 4MB + +CIRCUITPY_ESP_PSRAM_SIZE = 2MB +CIRCUITPY_ESP_PSRAM_MODE = qio +CIRCUITPY_ESP_PSRAM_FREQ = 80m + +CIRCUITPY_ESPCAMERA = 0 +CIRCUITPY_BITMAPFILTER = 0 +CIRCUITPY_CODEOP = 0 +CIRCUITPY_PARALLELDISPLAYBUS = 0 + + +# Include these Python libraries in firmware. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HTTPServer +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_IRRemote +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_BLE diff --git a/ports/espressif/boards/elio_eliobot_s3/pins.c b/ports/espressif/boards/elio_eliobot_s3/pins.c new file mode 100644 index 00000000000..79f6e83aa61 --- /dev/null +++ b/ports/espressif/boards/elio_eliobot_s3/pins.c @@ -0,0 +1,134 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Romain Boutrois +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/board/__init__.h" + +static const mp_rom_map_elem_t board_module_globals_table[] = { + CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS + + { MP_ROM_QSTR(MP_QSTR_IO0), MP_ROM_PTR(&pin_GPIO0) }, + + // GPIO1 : NeoPixel (WS2812B) + { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO1) }, + + // GPIO2 : Eyes LED connector (J3) + { MP_ROM_QSTR(MP_QSTR_IO2), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_EYES), MP_ROM_PTR(&pin_GPIO2) }, + + // GPIO3 : Battery voltage sense (ADC, 1:2 divider) + { MP_ROM_QSTR(MP_QSTR_IO3), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_BATTERY), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_VBAT_SENSE), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_VOLTAGE_MONITOR), MP_ROM_PTR(&pin_GPIO3) }, + + // GPIO4-7 : IR obstacle sensor outputs (TSSP77038) + { MP_ROM_QSTR(MP_QSTR_IO4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_IR_OUT_1), MP_ROM_PTR(&pin_GPIO4) }, + + { MP_ROM_QSTR(MP_QSTR_IO5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_IR_OUT_2), MP_ROM_PTR(&pin_GPIO5) }, + + { MP_ROM_QSTR(MP_QSTR_IO6), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_IR_OUT_3), MP_ROM_PTR(&pin_GPIO6) }, + + { MP_ROM_QSTR(MP_QSTR_IO7), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_IR_OUT_4), MP_ROM_PTR(&pin_GPIO7) }, + + // GPIO8-9 : I2C bus + { MP_ROM_QSTR(MP_QSTR_IO8), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO8) }, + + { MP_ROM_QSTR(MP_QSTR_IO9), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO9) }, + + // GPIO10-14 : Line sensors (J1) + { MP_ROM_QSTR(MP_QSTR_IO10), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_LINE_1), MP_ROM_PTR(&pin_GPIO10) }, + + { MP_ROM_QSTR(MP_QSTR_IO11), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_LINE_2), MP_ROM_PTR(&pin_GPIO11) }, + + { MP_ROM_QSTR(MP_QSTR_IO12), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_LINE_3), MP_ROM_PTR(&pin_GPIO12) }, + + { MP_ROM_QSTR(MP_QSTR_IO13), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_LINE_4), MP_ROM_PTR(&pin_GPIO13) }, + + { MP_ROM_QSTR(MP_QSTR_IO14), MP_ROM_PTR(&pin_GPIO14) }, + { MP_ROM_QSTR(MP_QSTR_LINE_5), MP_ROM_PTR(&pin_GPIO14) }, + + // GPIO15-16 : free (no 32 kHz crystal fitted) + { MP_ROM_QSTR(MP_QSTR_IO15), MP_ROM_PTR(&pin_GPIO15) }, + { MP_ROM_QSTR(MP_QSTR_IO16), MP_ROM_PTR(&pin_GPIO16) }, + + // GPIO17 : Buzzer + { MP_ROM_QSTR(MP_QSTR_IO17), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_BUZZER), MP_ROM_PTR(&pin_GPIO17) }, + + // GPIO18 : Line sensor LED enable + { MP_ROM_QSTR(MP_QSTR_IO18), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_LINE_LED), MP_ROM_PTR(&pin_GPIO18) }, + + // GPIO19 : USB_D- + // GPIO20 : USB_D+ + + // GPIO21 : VBUS sense (5V present, 1:2 divider) + { MP_ROM_QSTR(MP_QSTR_VBUS), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_VBUS_SENSE), MP_ROM_PTR(&pin_GPIO21) }, + + // GPIO26-32 : internal flash/PSRAM SPI — not available as GPIO + + // GPIO33 : Line sensor command output (J1) + { MP_ROM_QSTR(MP_QSTR_IO33), MP_ROM_PTR(&pin_GPIO33) }, + { MP_ROM_QSTR(MP_QSTR_LINE_CMD), MP_ROM_PTR(&pin_GPIO33) }, + + // GPIO34 : IR obstacle sensor 38kHz modulation output + { MP_ROM_QSTR(MP_QSTR_IO34), MP_ROM_PTR(&pin_GPIO34) }, + { MP_ROM_QSTR(MP_QSTR_IR_CMD), MP_ROM_PTR(&pin_GPIO34) }, + + // GPIO35-38 : Motor driver (DRV8833) + { MP_ROM_QSTR(MP_QSTR_IO35), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_BIN2), MP_ROM_PTR(&pin_GPIO35) }, + + { MP_ROM_QSTR(MP_QSTR_IO36), MP_ROM_PTR(&pin_GPIO36) }, + { MP_ROM_QSTR(MP_QSTR_AIN1), MP_ROM_PTR(&pin_GPIO36) }, + + { MP_ROM_QSTR(MP_QSTR_IO37), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_BIN1), MP_ROM_PTR(&pin_GPIO37) }, + + { MP_ROM_QSTR(MP_QSTR_IO38), MP_ROM_PTR(&pin_GPIO38) }, + { MP_ROM_QSTR(MP_QSTR_AIN2), MP_ROM_PTR(&pin_GPIO38) }, + + // GPIO39-41 : SPI header (J8) via JTAG pins MTCK/MTDO/MTDI + { MP_ROM_QSTR(MP_QSTR_IO39), MP_ROM_PTR(&pin_GPIO39) }, + { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO39) }, + + { MP_ROM_QSTR(MP_QSTR_IO40), MP_ROM_PTR(&pin_GPIO40) }, + { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO40) }, + + { MP_ROM_QSTR(MP_QSTR_IO41), MP_ROM_PTR(&pin_GPIO41) }, + { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO41) }, + + { MP_ROM_QSTR(MP_QSTR_IO42), MP_ROM_PTR(&pin_GPIO42) }, + + // GPIO43-44 : UART (U0TXD / U0RXD) + { MP_ROM_QSTR(MP_QSTR_IO43), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO43) }, + + { MP_ROM_QSTR(MP_QSTR_IO44), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO44) }, + + // GPIO45 : SPI chip select (J8) + { MP_ROM_QSTR(MP_QSTR_IO45), MP_ROM_PTR(&pin_GPIO45) }, + { MP_ROM_QSTR(MP_QSTR_SS), MP_ROM_PTR(&pin_GPIO45) }, + + { MP_ROM_QSTR(MP_QSTR_IO46), MP_ROM_PTR(&pin_GPIO46) }, + + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/espressif/boards/elio_eliobot_s3/sdkconfig b/ports/espressif/boards/elio_eliobot_s3/sdkconfig new file mode 100644 index 00000000000..253a3984eb1 --- /dev/null +++ b/ports/espressif/boards/elio_eliobot_s3/sdkconfig @@ -0,0 +1,16 @@ +# +# Espressif IoT Development Framework Configuration +# +# +# Component config +# +# +# LWIP +# +CONFIG_LWIP_LOCAL_HOSTNAME="ELIOBOT" + +# end of LWIP + +# end of Component config + +# end of Espressif IoT Development Framework Configuration diff --git a/ports/espressif/boards/espressif_esp32s3_box_3/board.c b/ports/espressif/boards/espressif_esp32s3_box_3/board.c new file mode 100644 index 00000000000..20426b4bef4 --- /dev/null +++ b/ports/espressif/boards/espressif_esp32s3_box_3/board.c @@ -0,0 +1,97 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// SPDX-FileCopyrightText: Copyright (c) 2026 Moises Trovo +// SPDX-License-Identifier: MIT + +#include "supervisor/board.h" +#include "mpconfigboard.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "common-hal/microcontroller/Pin.h" +#include "py/mphal.h" +#include "driver/gpio.h" + +#include "shared-bindings/busio/SPI.h" +#include "shared-bindings/fourwire/FourWire.h" +#include "shared-module/displayio/__init__.h" +#include "shared-module/displayio/mipi_constants.h" + +// esp-bsp bsp/esp-box-3/esp-box-3.c vendor_specific_init[], with SWRESET +// prepended and MADCTL set to 0xC8 for CircuitPython's landscape orientation. +// The BSP's own 0x08 is portrait; it also calls +// esp_lcd_panel_mirror(panel, true, true), which CircuitPython folds into +// MADCTL. If the image is ever mirrored or rotated, 0x36 is the knob. +uint8_t display_init_sequence[] = { + 0x01, 0x80, 0x96, // SWRESET, delay 150ms + 0xC8, 0x03, 0xFF, 0x93, 0x42, // vendor unlock + 0xC0, 0x02, 0x0E, 0x0E, // power control + 0xC5, 0x01, 0xD0, // VCOM + 0xC1, 0x01, 0x02, + 0xB4, 0x01, 0x02, + 0xE0, 0x0F, 0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0A, 0x08, 0x17, 0x17, 0x0F, // positive gamma + 0xE1, 0x0F, 0x00, 0x28, 0x29, 0x01, 0x0D, 0x03, 0x3F, 0x33, 0x52, 0x04, 0x0F, 0x0E, 0x37, 0x38, 0x0F, // negative gamma + 0xB1, 0x02, 0x00, 0x1B, + 0x36, 0x01, 0xC8, // MADCTL: landscape, BGR + 0x3A, 0x01, 0x55, // COLMOD: 16bpp + 0xB7, 0x01, 0x06, + 0x11, 0x80, 0x78, // SLPOUT, delay 120ms + 0x29, 0x80, 0x78, // DISPON, delay 120ms +}; + +void board_init(void) { + // The panel reset (GPIO48, shared with the touch controller) is ACTIVE + // HIGH. FourWire assumes active low and leaves the pin high, which holds + // this panel in reset forever with no error. So assert it here, release + // it low, and hand FourWire reset = None. + common_hal_never_reset_pin(&pin_GPIO48); + config_pin_as_output_with_level(GPIO_NUM_48, true); + mp_hal_delay_ms(10); + config_pin_as_output_with_level(GPIO_NUM_48, false); + mp_hal_delay_ms(120); + + fourwire_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus; + busio_spi_obj_t *spi = &bus->inline_bus; + common_hal_busio_spi_construct(spi, &pin_GPIO7, &pin_GPIO6, NULL, false); + common_hal_busio_spi_never_reset(spi); + + bus->base.type = &fourwire_fourwire_type; + common_hal_fourwire_fourwire_construct(bus, + spi, + MP_OBJ_FROM_PTR(&pin_GPIO4), // LCD_DC + MP_OBJ_FROM_PTR(&pin_GPIO5), // LCD_CS + mp_const_none, // reset managed above, opposite polarity + 40000000, // Baudrate. BSP uses 40MHz; 24MHz is the fallback. + 0, // Polarity + 0); // Phase + + busdisplay_busdisplay_obj_t *display = &allocate_display()->display; + display->base.type = &busdisplay_busdisplay_type; + common_hal_busdisplay_busdisplay_construct(display, + bus, + 320, // Width + 240, // Height + 0, // column start + 0, // row start + 0, // rotation + 16, // Color depth + false, // Grayscale + false, // pixels in a byte share a row. Only valid for depths < 8 + 1, // bytes per cell. Only valid for depths < 8 + false, // reverse_pixels_in_byte. Only valid for depths < 8 + true, // reverse_pixels_in_word + MIPI_COMMAND_SET_COLUMN_ADDRESS, // Set column command + MIPI_COMMAND_SET_PAGE_ADDRESS, // Set row command + MIPI_COMMAND_WRITE_MEMORY_START, // Write memory command + display_init_sequence, + sizeof(display_init_sequence), + &pin_GPIO47, // backlight pin + NO_BRIGHTNESS_COMMAND, + 1.0f, // brightness + false, // single_byte_bounds + false, // data_as_commands + true, // auto_refresh + 60, // native_frames_per_second + true, // backlight_on_high + false, // SH1107_addressing + 50000); // backlight pwm frequency +} + +// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/espressif/boards/espressif_esp32s3_box_3/mpconfigboard.h b/ports/espressif/boards/espressif_esp32s3_box_3/mpconfigboard.h new file mode 100644 index 00000000000..9ded82db172 --- /dev/null +++ b/ports/espressif/boards/espressif_esp32s3_box_3/mpconfigboard.h @@ -0,0 +1,10 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// SPDX-FileCopyrightText: Copyright (c) 2026 Moises Trovo +// SPDX-License-Identifier: MIT + +#pragma once + +// Micropython setup + +#define MICROPY_HW_BOARD_NAME "ESP32-S3-Box-3" +#define MICROPY_HW_MCU_NAME "ESP32S3" diff --git a/ports/espressif/boards/espressif_esp32s3_box_3/mpconfigboard.mk b/ports/espressif/boards/espressif_esp32s3_box_3/mpconfigboard.mk new file mode 100644 index 00000000000..dbbee619732 --- /dev/null +++ b/ports/espressif/boards/espressif_esp32s3_box_3/mpconfigboard.mk @@ -0,0 +1,14 @@ +USB_VID = 0x303A +USB_PID = 0x7015 +USB_PRODUCT = "ESP32-S3-Box-3" +USB_MANUFACTURER = "Espressif" + +IDF_TARGET = esp32s3 + +CIRCUITPY_ESP_FLASH_SIZE = 16MB +CIRCUITPY_ESP_FLASH_MODE = qio +CIRCUITPY_ESP_FLASH_FREQ = 80m + +CIRCUITPY_ESP_PSRAM_SIZE = 16MB +CIRCUITPY_ESP_PSRAM_MODE = opi +CIRCUITPY_ESP_PSRAM_FREQ = 80m diff --git a/ports/espressif/boards/espressif_esp32s3_box_3/pins.c b/ports/espressif/boards/espressif_esp32s3_box_3/pins.c new file mode 100644 index 00000000000..ce1230f17d5 --- /dev/null +++ b/ports/espressif/boards/espressif_esp32s3_box_3/pins.c @@ -0,0 +1,61 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// SPDX-FileCopyrightText: Copyright (c) 2026 Moises Trovo +// SPDX-License-Identifier: MIT + +#include "shared-bindings/board/__init__.h" +#include "shared-module/displayio/__init__.h" + +static const mp_rom_map_elem_t board_module_globals_table[] = { + CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS + + // PMOD connector 1 (dock). IO2/IO6 are the USB D+/D- pair (GPIO20/GPIO19) + // and are not aliased. + { MP_ROM_QSTR(MP_QSTR_G42), MP_ROM_PTR(&pin_GPIO42) }, + { MP_ROM_QSTR(MP_QSTR_G39), MP_ROM_PTR(&pin_GPIO39) }, + { MP_ROM_QSTR(MP_QSTR_G40), MP_ROM_PTR(&pin_GPIO40) }, + { MP_ROM_QSTR(MP_QSTR_G21), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_G38), MP_ROM_PTR(&pin_GPIO38) }, + { MP_ROM_QSTR(MP_QSTR_G41), MP_ROM_PTR(&pin_GPIO41) }, + + // PMOD connector 2 (dock) + { MP_ROM_QSTR(MP_QSTR_G13), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_G9), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_G12), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_G44), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_G10), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_G14), MP_ROM_PTR(&pin_GPIO14) }, + { MP_ROM_QSTR(MP_QSTR_G11), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_G43), MP_ROM_PTR(&pin_GPIO43) }, + + // LCD & touch + { MP_ROM_QSTR(MP_QSTR_LCD_DC), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_LCD_CS), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_LCD_MOSI), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_LCD_SCK), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_LCD_RST), MP_ROM_PTR(&pin_GPIO48) }, + { MP_ROM_QSTR(MP_QSTR_LCD_BACKLIGHT), MP_ROM_PTR(&pin_GPIO47) }, + { MP_ROM_QSTR(MP_QSTR_CTP_INT), MP_ROM_PTR(&pin_GPIO3) }, + + // Audio + { MP_ROM_QSTR(MP_QSTR_I2S_MCLK), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_I2S_SCLK), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_I2S_LRCK), MP_ROM_PTR(&pin_GPIO45) }, + { MP_ROM_QSTR(MP_QSTR_I2S_CODEC_DSDIN), MP_ROM_PTR(&pin_GPIO15) }, + { MP_ROM_QSTR(MP_QSTR_I2S_ADC_SDOUT), MP_ROM_PTR(&pin_GPIO16) }, + { MP_ROM_QSTR(MP_QSTR_PA_CTRL), MP_ROM_PTR(&pin_GPIO46) }, + { MP_ROM_QSTR(MP_QSTR_MUTE_STATUS), MP_ROM_PTR(&pin_GPIO1) }, + + // Internal I2C bus (codec, touch, IMU, AHT30) + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO18) }, + + // Dock I2C bus + { MP_ROM_QSTR(MP_QSTR_SCL2), MP_ROM_PTR(&pin_GPIO40) }, + { MP_ROM_QSTR(MP_QSTR_SDA2), MP_ROM_PTR(&pin_GPIO41) }, + + // Boot button, also usable as a software button + { MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_PTR(&pin_GPIO0) }, + + { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display) } +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/espressif/boards/espressif_esp32s3_box_3/sdkconfig b/ports/espressif/boards/espressif_esp32s3_box_3/sdkconfig new file mode 100644 index 00000000000..e9628662160 --- /dev/null +++ b/ports/espressif/boards/espressif_esp32s3_box_3/sdkconfig @@ -0,0 +1,14 @@ +# +# Espressif IoT Development Framework Configuration +# +# +# Component config +# +# +# LWIP +# +# end of LWIP + +# end of Component config + +# end of Espressif IoT Development Framework Configuration diff --git a/ports/espressif/boards/mixgo_ce_serial/board.c b/ports/espressif/boards/mixgo_ce_serial/board.c index e83f3b69679..e0d9f9530f2 100644 --- a/ports/espressif/boards/mixgo_ce_serial/board.c +++ b/ports/espressif/boards/mixgo_ce_serial/board.c @@ -16,8 +16,8 @@ void board_init(void) { mp_import_stat_t stat_b = mp_import_stat("boot.py"); if (stat_b != MP_IMPORT_STAT_FILE) { - fs_user_mount_t *fs_mount = filesystem_circuitpy(); - FATFS *fatfs = &fs_mount->fatfs; + supervisor_vfs_t *fs_mount = filesystem_circuitpy(); + FATFS *fatfs = &fs_mount->fat.fatfs; FIL fs; UINT char_written = 0; const byte buffer[] = "#Serial port upload mode\nimport storage\nstorage.remount(\"/\", False)\nstorage.disable_usb_drive()\n"; diff --git a/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/board.c b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/board.c new file mode 100644 index 00000000000..0a3f930efbe --- /dev/null +++ b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/board.c @@ -0,0 +1,100 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Benjamin Shockley +// SPDX-FileCopyrightText: Copyright (c) 2026 David Glaude +// +// SPDX-License-Identifier: MIT +// +// Modified from waveshare_esp32_c6_lcd_1_47 to support waveshare_esp32_c5_lcd_1_47 + +#include "supervisor/board.h" +#include "mpconfigboard.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/busio/SPI.h" +#include "shared-bindings/fourwire/FourWire.h" +#include "shared-bindings/busdisplay/BusDisplay.h" +#include "shared-module/displayio/__init__.h" +#include "shared-module/displayio/mipi_constants.h" +#include "shared-bindings/board/__init__.h" + +#define DELAY 0x80 + +// Driver: ST7789V3, Panel: LBS147TC-IF15 (172x320 RGB) +uint8_t display_init_sequence[] = { + 0x01, 0 | DELAY, 120, + 0x11, 0 | DELAY, 120, + 0x13, 0, + 0x36, 1, 0x00, + 0x3A, 1 | DELAY, 0x05, 10, + 0xB2, 5, 0x0C, 0x0C, 0x00, 0x33, 0x33, + 0xB7, 1, 0x35, + 0xBB, 1, 0x20, + 0xC0, 1, 0x2C, + 0xC2, 2, 0x01, 0xFF, + 0xC3, 1, 0x13, + 0xC4, 1, 0x20, + 0xC6, 1, 0x0F, + 0xD0, 2, 0xA4, 0xA1, + 0xE0, 14, 0xF0, 0x00, 0x04, 0x04, 0x04, 0x05, 0x29, 0x33, 0x3E, 0x38, 0x12, 0x12, 0x28, 0x30, + 0xE1, 14, 0xF0, 0x07, 0x0A, 0x0D, 0x0B, 0x07, 0x28, 0x33, 0x3E, 0x36, 0x14, 0x14, 0x29, 0x32, + 0x21, 0, + 0x29, 0 | DELAY, 255, +}; + +static void display_init(void) { + busio_spi_obj_t *spi = common_hal_board_create_spi(0); + fourwire_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus; + bus->base.type = &fourwire_fourwire_type; + + common_hal_fourwire_fourwire_construct( + bus, + spi, + MP_OBJ_FROM_PTR(&pin_GPIO24), // LCD_DC + MP_OBJ_FROM_PTR(&pin_GPIO23), // LCD_CS + MP_OBJ_FROM_PTR(&pin_GPIO26), // LCD_RST + 40000000, // Baudrate (40 MHz) + 0, // Polarity + 0 // Phase + ); + + busdisplay_busdisplay_obj_t *display = &allocate_display()->display; + display->base.type = &busdisplay_busdisplay_type; + + common_hal_busdisplay_busdisplay_construct( + display, + bus, + 172, // Native width + 320, // Native height + 34, // Column start offset + 0, // Row start offset + 0, // Native rotation: 0 (portrait) + 16, // Color depth + false, // Grayscale + false, // Pixels in byte share row + 1, // Bytes per cell + false, // Reverse pixels in byte + true, // Reverse pixels in word + MIPI_COMMAND_SET_COLUMN_ADDRESS, + MIPI_COMMAND_SET_PAGE_ADDRESS, + MIPI_COMMAND_WRITE_MEMORY_START, + display_init_sequence, + sizeof(display_init_sequence), + &pin_GPIO10, // Backlight pin (LCD_BL) + NO_BRIGHTNESS_COMMAND, + 1.0f, // Brightness + false, // Single byte bounds + false, // Data as commands + true, // Auto refresh + 60, // Native FPS + true, // Backlight active high + false, // SH1107 addressing + 50000 // Backlight PWM frequency + ); + + // Switch to landscape: swaps width/height to 320x172 and recalculates RAM offsets + common_hal_busdisplay_busdisplay_set_rotation(display, 270); +} + +void board_init(void) { + display_init(); +} diff --git a/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/mpconfigboard.h b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/mpconfigboard.h new file mode 100644 index 00000000000..11fba7dbe78 --- /dev/null +++ b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/mpconfigboard.h @@ -0,0 +1,34 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Benjamin Shockley +// SPDX-FileCopyrightText: Copyright (c) 2026 David Glaude +// +// SPDX-License-Identifier: MIT +// +// Modified from waveshare_esp32_c6_lcd_1_47 to support waveshare_esp32_c5_lcd_1_47 + +#pragma once + +// Micropython setup + +#define MICROPY_HW_BOARD_NAME "Waveshare ESP32-C5 LCD 1.47" +#define MICROPY_HW_MCU_NAME "ESP32-C5" + +// For entering safe mode, use BOOT button +#define CIRCUITPY_BOOT_BUTTON (&pin_GPIO28) + +// Waveshare onboard NeoPixel on GPIO8 +#define CIRCUITPY_STATUS_LED_POWER (&pin_GPIO8) +#define MICROPY_HW_NEOPIXEL (&pin_GPIO8) +#define MICROPY_HW_NEOPIXEL_COUNT (1) + +// Default SPI bus definitions (shared between LCD and TF card) +#define DEFAULT_SPI_BUS_SCK (&pin_GPIO7) +#define DEFAULT_SPI_BUS_MOSI (&pin_GPIO6) +#define DEFAULT_SPI_BUS_MISO (&pin_GPIO5) + +// Explanation of how a user got into safe mode +#define BOARD_USER_SAFE_MODE_ACTION MP_ERROR_TEXT("You pressed the BOOT button at start up.") + +// Reduce wifi.radio.tx_power due to the antenna design of this board +#define CIRCUITPY_WIFI_DEFAULT_TX_POWER (15) diff --git a/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/mpconfigboard.mk b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/mpconfigboard.mk new file mode 100644 index 00000000000..5a7b1521245 --- /dev/null +++ b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/mpconfigboard.mk @@ -0,0 +1,16 @@ +USB_PRODUCT = "Waveshare ESP32-C5 LCD 1.47" +USB_MANUFACTURER = "Waveshare" + +CIRCUITPY_CREATOR_ID = 0x000C303A +CIRCUITPY_CREATION_ID = 0x00C50002 + +IDF_TARGET = esp32c5 + +CIRCUITPY_ESP_FLASH_MODE = qio +CIRCUITPY_ESP_FLASH_FREQ = 80m +CIRCUITPY_ESP_FLASH_SIZE = 4MB + +CIRCUITPY_NEOPIXEL_WRITE = 1 +CIRCUITPY_BUSDISPLAY = 1 +CIRCUITPY_FOURWIRE = 1 +CIRCUITPY_4MB_FLASH_LARGE_USER_FS_LAYOUT = 1 diff --git a/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/pins.c b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/pins.c new file mode 100644 index 00000000000..15f67bca223 --- /dev/null +++ b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/pins.c @@ -0,0 +1,62 @@ +#include "shared-bindings/board/__init__.h" +#include "shared-module/displayio/__init__.h" + +static const mp_rom_map_elem_t board_module_globals_table[] = { + CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS + + // Boot / User button + { MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_PTR(&pin_GPIO28) }, + { MP_ROM_QSTR(MP_QSTR_BOOT0), MP_ROM_PTR(&pin_GPIO28) }, + { MP_ROM_QSTR(MP_QSTR_IO28), MP_ROM_PTR(&pin_GPIO28) }, + + // General purpose exposed GPIOs + { MP_ROM_QSTR(MP_QSTR_IO0), MP_ROM_PTR(&pin_GPIO0) }, + + // Status RGB LED + { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_IO8), MP_ROM_PTR(&pin_GPIO8) }, + + // Primary SPI bus lines + { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO5) }, + + // LCD Dedicated Aliases + { MP_ROM_QSTR(MP_QSTR_LCD_CLK), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_LCD_DIN), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_LCD_MOSI), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_LCD_CS), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_LCD_DC), MP_ROM_PTR(&pin_GPIO24) }, + { MP_ROM_QSTR(MP_QSTR_LCD_RST), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_LCD_BL), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_BACKLIGHT), MP_ROM_PTR(&pin_GPIO10) }, + + // MicroSD (TF Card) Dedicated Aliases + { MP_ROM_QSTR(MP_QSTR_SD_CS), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_SD_CLK), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_SD_MOSI), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_SD_MISO), MP_ROM_PTR(&pin_GPIO5) }, + + // General purpose exposed GPIOs + { MP_ROM_QSTR(MP_QSTR_IO1), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_IO2), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_IO3), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_IO4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_IO5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_IO6), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_IO7), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_IO9), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_IO10), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_IO23), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_IO24), MP_ROM_PTR(&pin_GPIO24) }, + { MP_ROM_QSTR(MP_QSTR_IO25), MP_ROM_PTR(&pin_GPIO25) }, + { MP_ROM_QSTR(MP_QSTR_IO26), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_IO27), MP_ROM_PTR(&pin_GPIO27) }, + + // Shared SPI bus singleton + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, + + // Built-in Display + { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display) }, +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/sdkconfig b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/sdkconfig new file mode 100644 index 00000000000..b8cf0e97b5a --- /dev/null +++ b/ports/espressif/boards/waveshare_esp32_c5_lcd_1_47/sdkconfig @@ -0,0 +1 @@ +# Everything is default or implied by mpconfigboard.mk diff --git a/ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.h b/ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.h index 7a67f925ab1..979e17a2817 100644 --- a/ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.h +++ b/ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.h @@ -11,10 +11,6 @@ #define CIRCUITPY_BOARD_I2C (0) #define CIRCUITPY_BOARD_I2C_PIN {{.scl = &pin_GPIO48, .sda = &pin_GPIO47}} -// Display refresh buffer: 8192 bytes = 2048 uint32_t words on stack. -// ESP32-S3 main task stack is 24KB; verified safe with this board. -#define CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE (8192) - // AMOLED Display (displayio + qspibus path) - initialized in board_init() #define CIRCUITPY_BOARD_DISPLAY (1) #define CIRCUITPY_LCD_CS (&pin_GPIO9) diff --git a/ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.mk b/ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.mk index f901246804c..022525621ac 100644 --- a/ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.mk +++ b/ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.mk @@ -33,3 +33,7 @@ CIRCUITPY_TOUCHIO = 0 # SD card via SDMMC interface CIRCUITPY_SDIOIO = 1 + +# Display refresh buffer: 8192 bytes on the stack. +# ESP32-S3 main task stack is 24KB; verified safe with this board. +CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE = 8192 diff --git a/ports/espressif/boards/yoto_mini_2024/board.c b/ports/espressif/boards/yoto_mini_2024/board.c index fb6a96a957d..aa331e51c2f 100644 --- a/ports/espressif/boards/yoto_mini_2024/board.c +++ b/ports/espressif/boards/yoto_mini_2024/board.c @@ -191,8 +191,8 @@ void board_init(void) { } common_hal_sdioio_sdcard_never_reset(&sdmmc); - filesystem_set_concurrent_write_protection(vfs, true); - filesystem_set_writable_by_usb(vfs, false); + filesystem_set_concurrent_write_protection((supervisor_vfs_t *)vfs, true); + filesystem_set_writable_by_usb((supervisor_vfs_t *)vfs, false); mp_vfs_mount_t *sdcard_vfs = &_sdcard_vfs; sdcard_vfs->str = "/sd"; diff --git a/ports/espressif/boards/yoto_player_v3/board.c b/ports/espressif/boards/yoto_player_v3/board.c index fd6ee40b9f3..a2f943b0d18 100644 --- a/ports/espressif/boards/yoto_player_v3/board.c +++ b/ports/espressif/boards/yoto_player_v3/board.c @@ -136,8 +136,8 @@ void board_init(void) { } common_hal_sdioio_sdcard_never_reset(&sdmmc); - filesystem_set_concurrent_write_protection(vfs, true); - filesystem_set_writable_by_usb(vfs, false); + filesystem_set_concurrent_write_protection((supervisor_vfs_t *)vfs, true); + filesystem_set_writable_by_usb((supervisor_vfs_t *)vfs, false); mp_vfs_mount_t *sdcard_vfs = &_sdcard_vfs; sdcard_vfs->str = "/sd"; diff --git a/ports/espressif/common-hal/_bleio/Adapter.c b/ports/espressif/common-hal/_bleio/Adapter.c index e97c84b9b95..34122a1048c 100644 --- a/ports/espressif/common-hal/_bleio/Adapter.c +++ b/ports/espressif/common-hal/_bleio/Adapter.c @@ -32,6 +32,7 @@ #include "nimble/nimble_port_freertos.h" #include "host/ble_gap.h" #include "host/ble_gatt.h" +#include "host/ble_sm.h" #include "host/util/util.h" #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" @@ -145,7 +146,13 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC; ble_hs_cfg.sm_mitm = 0; - ble_hs_cfg.sm_sc = 0; + // LE Secure Connections is always offered. It's negotiated per pairing (a peer + // that only does legacy pairing still works) and is strictly better crypto, so + // there's no downside for existing "Just Works" users. Numeric-comparison pairing + // needs it. IO capability and the MITM requirement stay at the legacy defaults + // above until an attribute is constructed with a *_WITH_MITM permission, which + // raises them - see bleio_adapter_enable_mitm_pairing(). + ble_hs_cfg.sm_sc = 1; /* Stores the IRK */ ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID; ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID; @@ -197,6 +204,15 @@ bool common_hal_bleio_adapter_get_enabled(bleio_adapter_obj_t *self) { return xTaskGetHandle("nimble_host") != NULL; } +void bleio_adapter_enable_mitm_pairing(void) { + // ble_hs_cfg is a plain global read by the SM code at pairing time, so bumping it + // here (from Characteristic / Descriptor construction, after the adapter is up) takes + // effect for every subsequent pairing. Idempotent and one-way for the lifetime of + // the adapter - a characteristic that needs authentication doesn't stop needing it. + ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_YES_NO; + ble_hs_cfg.sm_mitm = 1; +} + bleio_address_obj_t *common_hal_bleio_adapter_get_address(bleio_adapter_obj_t *self) { uint8_t address_bytes[6]; uint8_t address_type = BLE_ADDR_RANDOM; diff --git a/ports/espressif/common-hal/_bleio/Adapter.h b/ports/espressif/common-hal/_bleio/Adapter.h index 40f24cfcdfe..31bd853f2b4 100644 --- a/ports/espressif/common-hal/_bleio/Adapter.h +++ b/ports/espressif/common-hal/_bleio/Adapter.h @@ -44,3 +44,10 @@ typedef struct { void bleio_adapter_gc_collect(bleio_adapter_obj_t *adapter); void bleio_adapter_reset(bleio_adapter_obj_t *adapter); + +// Raise the SM pairing configuration so an authenticated (MITM-protected) bond can be +// formed: advertise DISPLAY_YESNO IO capability and require MITM. Called from +// Characteristic / Descriptor construction when an attribute is given a *_WITH_MITM +// permission. A no-op-safe idempotent bump; devices with no such attribute keep the +// legacy "Just Works" defaults set in common_hal_bleio_adapter_set_enabled(). +void bleio_adapter_enable_mitm_pairing(void); diff --git a/ports/espressif/common-hal/_bleio/Characteristic.c b/ports/espressif/common-hal/_bleio/Characteristic.c index 9436376a811..6ea78629569 100644 --- a/ports/espressif/common-hal/_bleio/Characteristic.c +++ b/ports/espressif/common-hal/_bleio/Characteristic.c @@ -153,10 +153,6 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, if ((props & CHAR_PROP_WRITE_NO_RESPONSE) != 0) { self->flags |= BLE_GATT_CHR_F_WRITE_NO_RSP; } - if (read_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_ENC_WITH_MITM || - read_perm == SECURITY_MODE_SIGNED_WITH_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("MITM security not supported")); - } // The BLE_GATT_CHR_F_NOTIFY_INDICATE_* flags are set below to require encryption or // authentication when writing the auto-generated CCCD, if reading the // characteristic requires it. This matches the nordic port behavior. @@ -164,18 +160,30 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, // so an unpaired central can subscribe and nothing ever requires it to pair. // // TODO: This behavior was fixed in NimBLE 1.10.0. ESP-IDF 6.0.1 uses a fork of NimBLE. + // + // NimBLE's GATT layer has no dedicated "LESC" or "signed" characteristic flag: + // ENC_WITH_MITM, LESC_ENC_WITH_MITM and the SIGNED modes all map to the _AUTHEN + // flags, i.e. "require a man-in-the-middle-protected link". A *_WITH_MITM permission + // additionally makes the adapter offer numeric-comparison pairing (below) so such a + // link can actually be established. if (read_perm == SECURITY_MODE_ENC_NO_MITM) { self->flags |= BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; } - if (read_perm == SECURITY_MODE_SIGNED_NO_MITM) { + if (read_perm == SECURITY_MODE_ENC_WITH_MITM || read_perm == SECURITY_MODE_LESC_ENC_WITH_MITM || + read_perm == SECURITY_MODE_SIGNED_NO_MITM || read_perm == SECURITY_MODE_SIGNED_WITH_MITM) { self->flags |= BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHEN; } if (write_perm == SECURITY_MODE_ENC_NO_MITM) { self->flags |= BLE_GATT_CHR_F_WRITE_ENC; } - if (write_perm == SECURITY_MODE_SIGNED_NO_MITM) { + if (write_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_LESC_ENC_WITH_MITM || + write_perm == SECURITY_MODE_SIGNED_NO_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) { self->flags |= BLE_GATT_CHR_F_WRITE_AUTHEN; } + if (bleio_attribute_security_mode_requires_mitm(read_perm) || + bleio_attribute_security_mode_requires_mitm(write_perm)) { + bleio_adapter_enable_mitm_pairing(); + } // If max_length is 0, then no storage is allocated. if (max_length > 0) { diff --git a/ports/espressif/common-hal/_bleio/Connection.c b/ports/espressif/common-hal/_bleio/Connection.c index a063f3ee1a8..ff8a4402001 100644 --- a/ports/espressif/common-hal/_bleio/Connection.c +++ b/ports/espressif/common-hal/_bleio/Connection.c @@ -34,6 +34,7 @@ #include "freertos/queue.h" #include "host/ble_att.h" +#include "host/ble_sm.h" #include "host/ble_store.h" // Uncomment to turn on debug logging just in this file. @@ -47,6 +48,7 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) case BLE_GAP_EVENT_DISCONNECT: { connection->conn_handle = BLEIO_HANDLE_INVALID; connection->pair_status = PAIR_NOT_PAIRED; + connection->mitm_protected = false; #if CIRCUITPY_VERBOSE_BLE mp_printf(&mp_plat_print, "event->disconnect.reason: 0x%x\n", event->disconnect.reason); @@ -79,6 +81,34 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) ble_gap_conn_find(event->enc_change.conn_handle, &desc); if (desc.sec_state.encrypted) { connection->pair_status = PAIR_PAIRED; + } else if (connection->pair_status == PAIR_WAITING_NUMCMP) { + // Numeric comparison was rejected, or SM otherwise failed. + connection->pair_status = PAIR_NOT_PAIRED; + } + // .authenticated is set once the MITM leg (numeric comparison / passkey entry) + // completes; a plain "Just Works" bond leaves it clear. Surfaced as + // Connection.authenticated. + connection->mitm_protected = desc.sec_state.authenticated; + break; + } + case BLE_GAP_EVENT_PASSKEY_ACTION: { + // The peer started MITM pairing. An attribute was constructed with a + // *_WITH_MITM permission, which made the adapter advertise DISPLAY_YESNO IO + // capability (bleio_adapter_enable_mitm_pairing()), so NimBLE asks us to + // confirm a 6-digit numeric-comparison value. Stash it and enter + // PAIR_WAITING_NUMCMP; Python reads it via Connection.numeric_comparison + // and answers with confirm_pairing(). + if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + connection->pairing_numcmp = event->passkey.params.numcmp; + connection->pair_status = PAIR_WAITING_NUMCMP; + } else { + // Passkey entry / OOB: a display + yes/no button can't service these. + // Fail fast instead of letting SM hang until its ~30 s timeout. + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "unsupported passkey action %d; terminating\n", + event->passkey.params.action); + #endif + ble_gap_terminate(event->passkey.conn_handle, BLE_ERR_AUTH_FAIL); } break; } @@ -167,6 +197,40 @@ void common_hal_bleio_connection_pair(bleio_connection_internal_t *self, bool bo } } +// LE Secure Connections numeric comparison. Unlike pair(), these don't block: the peer +// (a central) drives the SM procedure; we just observe the pending value and inject the +// yes/no. BLE_GAP_EVENT_PASSKEY_ACTION sets pair_status = PAIR_WAITING_NUMCMP. +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { + if (self->connection == NULL || self->connection->pair_status != PAIR_WAITING_NUMCMP) { + return mp_const_none; + } + return mp_obj_new_int_from_uint(self->connection->pairing_numcmp); +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + if (self->connection == NULL) { + mp_raise_ConnectionError(MP_ERROR_TEXT("Not connected")); + } + if (self->connection->pair_status != PAIR_WAITING_NUMCMP) { + mp_raise_bleio_BluetoothError(MP_ERROR_TEXT("No pairing in progress")); + } + struct ble_sm_io io = { + .action = BLE_SM_IOACT_NUMCMP, + .numcmp_accept = accept, + }; + CHECK_NIMBLE_ERROR(ble_sm_inject_io(self->connection->conn_handle, &io)); + // SM proceeds from here. On accept, ENC_CHANGE moves us to PAIR_PAIRED; on reject it + // won't, so drop straight back to PAIR_NOT_PAIRED rather than waiting for the peer. + self->connection->pair_status = accept ? PAIR_WAITING : PAIR_NOT_PAIRED; +} + +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + if (self->connection == NULL) { + return false; + } + return self->connection->mitm_protected; +} + mp_float_t common_hal_bleio_connection_get_connection_interval(bleio_connection_internal_t *self) { while (self->conn_params_updating && !mp_hal_is_interrupted()) { RUN_BACKGROUND_TASKS; diff --git a/ports/espressif/common-hal/_bleio/Connection.h b/ports/espressif/common-hal/_bleio/Connection.h index 8d8e727e3da..143cc64d28a 100644 --- a/ports/espressif/common-hal/_bleio/Connection.h +++ b/ports/espressif/common-hal/_bleio/Connection.h @@ -22,6 +22,11 @@ typedef enum { PAIR_NOT_PAIRED, PAIR_WAITING, + // The peer is running LE Secure Connections numeric-comparison pairing and NimBLE is + // waiting for us to confirm the 6-digit value. Python reads it from + // Connection.numeric_comparison and answers with confirm_pairing(). This is a + // peer-driven, non-blocking path; the blocking pair() does not use it. + PAIR_WAITING_NUMCMP, PAIR_PAIRED, } pair_status_t; @@ -50,6 +55,13 @@ typedef struct { uint16_t ediv; volatile pair_status_t pair_status; uint8_t sec_status; // Internal security status. + // The 6-digit LE Secure Connections numeric-comparison value stashed by + // BLE_GAP_EVENT_PASSKEY_ACTION. Only valid while pair_status == PAIR_WAITING_NUMCMP. + volatile uint32_t pairing_numcmp; + // True once the link is encrypted AND authenticated against MITM (numeric comparison + // or passkey entry completed). A plain encrypted "Just Works" link leaves this false. + // Surfaced to Python as Connection.authenticated. + volatile bool mitm_protected; mp_obj_t connection_obj; volatile bool conn_params_updating; uint16_t mtu; diff --git a/ports/espressif/common-hal/_bleio/Descriptor.c b/ports/espressif/common-hal/_bleio/Descriptor.c index 3f2870740f1..617e862206f 100644 --- a/ports/espressif/common-hal/_bleio/Descriptor.c +++ b/ports/espressif/common-hal/_bleio/Descriptor.c @@ -15,6 +15,8 @@ #include "shared-bindings/_bleio/Service.h" #include "shared-bindings/_bleio/UUID.h" +#include "common-hal/_bleio/Adapter.h" + #include "host/ble_att.h" void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_characteristic_obj_t *characteristic, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo) { @@ -33,22 +35,26 @@ void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_c if (write_perm != SECURITY_MODE_NO_ACCESS) { self->flags |= BLE_ATT_F_WRITE; } - if (read_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_ENC_WITH_MITM || - read_perm == SECURITY_MODE_SIGNED_WITH_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("MITM security not supported")); - } + // MITM / LESC-MITM / SIGNED all map to the _AUTHEN flags (require an authenticated + // link); see the matching block in Characteristic.c. if (read_perm == SECURITY_MODE_ENC_NO_MITM) { self->flags |= BLE_ATT_F_READ_ENC; } - if (read_perm == SECURITY_MODE_SIGNED_NO_MITM) { + if (read_perm == SECURITY_MODE_ENC_WITH_MITM || read_perm == SECURITY_MODE_LESC_ENC_WITH_MITM || + read_perm == SECURITY_MODE_SIGNED_NO_MITM || read_perm == SECURITY_MODE_SIGNED_WITH_MITM) { self->flags |= BLE_ATT_F_READ_AUTHEN; } if (write_perm == SECURITY_MODE_ENC_NO_MITM) { self->flags |= BLE_ATT_F_WRITE_ENC; } - if (write_perm == SECURITY_MODE_SIGNED_NO_MITM) { + if (write_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_LESC_ENC_WITH_MITM || + write_perm == SECURITY_MODE_SIGNED_NO_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) { self->flags |= BLE_ATT_F_WRITE_AUTHEN; } + if (bleio_attribute_security_mode_requires_mitm(read_perm) || + bleio_attribute_security_mode_requires_mitm(write_perm)) { + bleio_adapter_enable_mitm_pairing(); + } const mp_int_t max_length_max = BLE_ATT_ATTR_MAX_LEN; if (max_length < 0 || max_length > max_length_max) { diff --git a/ports/espressif/common-hal/busio/SPI.c b/ports/espressif/common-hal/busio/SPI.c index b85f8cddd2a..40ba60dab40 100644 --- a/ports/espressif/common-hal/busio/SPI.c +++ b/ports/espressif/common-hal/busio/SPI.c @@ -97,6 +97,9 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, // Ensure the object starts in its deinit state. common_hal_busio_spi_mark_deinit(self); + #if CIRCUITPY_BUSIO_SPI_ASYNC + self->async_count = 0; + #endif const spi_bus_config_t bus_config = { .mosi_io_num = mosi != NULL ? mosi->number : -1, @@ -176,6 +179,10 @@ void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { RUN_BACKGROUND_TASKS; } + #if CIRCUITPY_BUSIO_SPI_ASYNC + common_hal_busio_spi_end(self); + #endif + // Mark as deinit early in case we are used in an interrupt. common_hal_reset_pin(self->clock); common_hal_busio_spi_mark_deinit(self); @@ -200,6 +207,9 @@ bool common_hal_busio_spi_configure(busio_spi_obj_t *self, bits == self->bits) { return true; } + #if CIRCUITPY_BUSIO_SPI_ASYNC + common_hal_busio_spi_end(self); + #endif spi_bus_remove_device(spi_handle[self->host_id]); set_spi_config(self, baudrate, polarity, phase, bits); return true; @@ -324,6 +334,45 @@ bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, return true; } +#if CIRCUITPY_BUSIO_SPI_ASYNC +void common_hal_busio_spi_write_start(busio_spi_obj_t *self, const uint8_t *data, size_t len, + circuitpy_async_flag_t *done) { + common_hal_busio_spi_end(self); + CIRCUITPY_ASYNC_FLAG_INIT(done); + self->async_done = done; + size_t chunks = (len + SPI_MAX_DMA_LEN - 1) / SPI_MAX_DMA_LEN; + // Short writes, other word sizes and writes needing more transactions than we keep go the + // normal way. + if (len <= 4 || self->bits != 8 || chunks > MP_ARRAY_SIZE(self->async_trans)) { + common_hal_busio_spi_write(self, data, len); + CIRCUITPY_ASYNC_FLAG_SET(done); + return; + } + for (size_t i = 0; i < chunks; i++) { + size_t offset = i * SPI_MAX_DMA_LEN; + spi_transaction_t *trans = &self->async_trans[i]; + memset(trans, 0, sizeof(*trans)); + trans->length = MIN(len - offset, SPI_MAX_DMA_LEN) * 8; + trans->tx_buffer = data + offset; + spi_device_queue_trans(spi_handle[self->host_id], trans, portMAX_DELAY); + } + self->async_count = chunks; +} + +void common_hal_busio_spi_end(busio_spi_obj_t *self) { + if (self->async_count == 0) { + return; + } + // No background tasks here: the caller holds the bus, and one of them may want it. + spi_transaction_t *trans; + while (self->async_count > 0) { + spi_device_get_trans_result(spi_handle[self->host_id], &trans, portMAX_DELAY); + self->async_count--; + } + CIRCUITPY_ASYNC_FLAG_SET(self->async_done); +} +#endif + uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t *self) { return self->baudrate; } diff --git a/ports/espressif/common-hal/busio/SPI.h b/ports/espressif/common-hal/busio/SPI.h index 4886ab173ab..37f1eba7f61 100644 --- a/ports/espressif/common-hal/busio/SPI.h +++ b/ports/espressif/common-hal/busio/SPI.h @@ -6,6 +6,8 @@ #pragma once +#include "py/mpconfig.h" +#include "supervisor/shared/async_flag.h" #include "driver/spi_master.h" #include "shared-bindings/microcontroller/Pin.h" @@ -25,4 +27,9 @@ typedef struct { uint32_t requested_baudrate; // Value passed to configure(); used for the cache-hit check. SemaphoreHandle_t mutex; + #if CIRCUITPY_BUSIO_SPI_ASYNC + spi_transaction_t async_trans[2]; // queued by write_start, collected by end + uint8_t async_count; + circuitpy_async_flag_t *async_done; + #endif } busio_spi_obj_t; diff --git a/ports/espressif/common-hal/espcamera/Camera.c b/ports/espressif/common-hal/espcamera/Camera.c index c8f693fa3f1..6b2cc6d9da2 100644 --- a/ports/espressif/common-hal/espcamera/Camera.c +++ b/ports/espressif/common-hal/espcamera/Camera.c @@ -47,7 +47,7 @@ static void maybe_claim_pin(const mcu_pin_obj_t *pin) { void common_hal_espcamera_camera_construct( espcamera_camera_obj_t *self, - uint8_t data_pins[8], + const mcu_pin_obj_t **data_pins, const mcu_pin_obj_t *external_clock_pin, const mcu_pin_obj_t *pixel_clock_pin, const mcu_pin_obj_t *vsync_pin, @@ -63,7 +63,7 @@ void common_hal_espcamera_camera_construct( camera_grab_mode_t grab_mode) { for (int i = 0; i < 8; i++) { - claim_pin_number(data_pins[i]); + claim_pin(data_pins[i]); } maybe_claim_pin(external_clock_pin); claim_pin(pixel_clock_pin); @@ -97,14 +97,14 @@ void common_hal_espcamera_camera_construct( self->camera_config.pin_sccb_scl = -1; self->camera_config.sccb_i2c_port = i2c->port; - self->camera_config.pin_d7 = data_pins[7]; - self->camera_config.pin_d6 = data_pins[6]; - self->camera_config.pin_d5 = data_pins[5]; - self->camera_config.pin_d4 = data_pins[4]; - self->camera_config.pin_d3 = data_pins[3]; - self->camera_config.pin_d2 = data_pins[2]; - self->camera_config.pin_d1 = data_pins[1]; - self->camera_config.pin_d0 = data_pins[0]; + self->camera_config.pin_d7 = common_hal_mcu_pin_number(data_pins[7]); + self->camera_config.pin_d6 = common_hal_mcu_pin_number(data_pins[6]); + self->camera_config.pin_d5 = common_hal_mcu_pin_number(data_pins[5]); + self->camera_config.pin_d4 = common_hal_mcu_pin_number(data_pins[4]); + self->camera_config.pin_d3 = common_hal_mcu_pin_number(data_pins[3]); + self->camera_config.pin_d2 = common_hal_mcu_pin_number(data_pins[2]); + self->camera_config.pin_d1 = common_hal_mcu_pin_number(data_pins[1]); + self->camera_config.pin_d0 = common_hal_mcu_pin_number(data_pins[0]); self->camera_config.pin_vsync = common_hal_mcu_pin_number(vsync_pin); self->camera_config.pin_href = common_hal_mcu_pin_number(href_pin); diff --git a/ports/espressif/common-hal/hardwarekey/HardwareKey.c b/ports/espressif/common-hal/hardwarekey/HardwareKey.c new file mode 100644 index 00000000000..a93a4346d59 --- /dev/null +++ b/ports/espressif/common-hal/hardwarekey/HardwareKey.c @@ -0,0 +1,88 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +// The one port-specific step: turn an eFuse key block into a PSA key id. +// Everything after that -- hmac_sha256(), verify_hmac_sha256() -- lives in +// shared-module/hardwarekey/HardwareKey.c. + +#include "common-hal/hardwarekey/__init__.h" +#include "common-hal/hardwarekey/board.h" + +#include "shared-module/hardwarekey/HardwareKey.h" + +#include "esp_efuse.h" + +// board.h hardcodes the slot count (enum values can't be used in #if); make sure +// it still matches this chip's eFuse layout. +_Static_assert(HARDWAREKEY_EFUSE_SLOT_COUNT == EFUSE_BLK_KEY_MAX - EFUSE_BLK_KEY0, + "eFuse key block count changed; update common-hal/hardwarekey/board.h"); + +// Pulls in MBEDTLS_CONFIG_FILE (esp_config.h), which is what defines +// ESP_HMAC_OPAQUE_DRIVER_ENABLED on HMAC-capable chips. Including only +// goes through the tf-psa-crypto config path and does NOT +// define it, so the opaque-driver header below would compile to nothing. +#include "mbedtls/build_info.h" +#include "psa/crypto.h" +// Public header of the ESP-IDF mbedtls component's PSA opaque-key driver for +// eFuse HMAC keys (components/mbedtls/port/psa_driver/include/). +#include "psa_crypto_driver_esp_hmac_opaque.h" + +#if !defined(ESP_HMAC_OPAQUE_DRIVER_ENABLED) +#error "hardwarekey requires the ESP-IDF PSA opaque HMAC driver (SOC_HMAC_SUPPORTED targets only)" +#endif + +// The ESP HMAC peripheral consumes a 256-bit eFuse key. +#define HMAC_KEY_BITS 256 + +// One PSA key is imported per eFuse block. The imports are volatile references +// (no key material) and survive a CircuitPython soft reset -- ESP-IDF initializes +// PSA once at boot and never frees it -- so this only runs once per block. +static psa_key_id_t import_efuse_hmac_key(mp_int_t slot) { + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&attr, PSA_KEY_TYPE_HMAC); + psa_set_key_bits(&attr, HMAC_KEY_BITS); + psa_set_key_algorithm(&attr, PSA_ALG_HMAC(PSA_ALG_SHA_256)); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_ESP_HMAC_VOLATILE); + + // Import data is a *reference* to the eFuse block, not key material. The + // driver independently re-checks the HMAC_UP purpose and refuses anything else. + esp_hmac_opaque_key_t keyref = { .efuse_key_id = (uint8_t)slot }; + + psa_key_id_t key_id = 0; + if (psa_import_key(&attr, (const uint8_t *)&keyref, sizeof(keyref), &key_id) != PSA_SUCCESS) { + return 0; + } + return key_id; +} + +bool hardwarekey_efuse_slot_load(mp_int_t slot, hardwarekey_hardwarekey_obj_t *key) { + key->key_slot = slot; + key->key_id = 0; + key->purpose = HARDWAREKEY_PURPOSE_UNUSED; + key->exportable = false; + + esp_efuse_block_t block = (esp_efuse_block_t)(EFUSE_BLK_KEY0 + slot); + if (esp_efuse_get_key_purpose(block) != ESP_EFUSE_KEY_PURPOSE_HMAC_UP) { + return false; + } + + // PSA is already initialized by ssl / hashlib, but psa_crypto_init() is + // idempotent and keeps hardwarekey working on a build with neither. + if (psa_crypto_init() != PSA_SUCCESS) { + return false; + } + + psa_key_id_t key_id = import_efuse_hmac_key(slot); + if (key_id == 0) { + return false; + } + + key->key_id = key_id; + key->purpose = HARDWAREKEY_PURPOSE_HMAC; + key->exportable = !esp_efuse_get_key_dis_read(block); + return true; +} diff --git a/ports/espressif/common-hal/hardwarekey/__init__.c b/ports/espressif/common-hal/hardwarekey/__init__.c new file mode 100644 index 00000000000..2d33ed9a4ab --- /dev/null +++ b/ports/espressif/common-hal/hardwarekey/__init__.c @@ -0,0 +1,30 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#include "common-hal/hardwarekey/__init__.h" +#include "common-hal/hardwarekey/board.h" + +#include "shared-bindings/hardwarekey/HardwareKey.h" + +// The objects board.EFUSE_KEY0 .. board.EFUSE_KEY point at (see board.h). +// Static, not heap: the board globals dict is const and outlives the GC heap +// across a soft reset, so these must too. No GC-traced pointers inside. +hardwarekey_hardwarekey_obj_t hardwarekey_efuse_keys[HARDWAREKEY_EFUSE_SLOT_COUNT]; + +// board.EFUSE_KEYn, for repr(). Can't assume MP_QSTR_EFUSE_KEY0 + n are contiguous. +static const qstr slot_names[HARDWAREKEY_EFUSE_SLOT_COUNT] = { + MP_QSTR_EFUSE_KEY0, MP_QSTR_EFUSE_KEY1, MP_QSTR_EFUSE_KEY2, + MP_QSTR_EFUSE_KEY3, MP_QSTR_EFUSE_KEY4, MP_QSTR_EFUSE_KEY5, +}; + +void espressif_hardwarekey_init(void) { + for (mp_int_t slot = 0; slot < HARDWAREKEY_EFUSE_SLOT_COUNT; slot++) { + hardwarekey_hardwarekey_obj_t *key = &hardwarekey_efuse_keys[slot]; + key->base.type = &hardwarekey_hardwarekey_type; + key->name = slot_names[slot]; + hardwarekey_efuse_slot_load(slot, key); + } +} diff --git a/ports/espressif/common-hal/hardwarekey/__init__.h b/ports/espressif/common-hal/hardwarekey/__init__.h new file mode 100644 index 00000000000..e6c55b51bd2 --- /dev/null +++ b/ports/espressif/common-hal/hardwarekey/__init__.h @@ -0,0 +1,20 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +#include "shared-module/hardwarekey/HardwareKey.h" + +// Probe every eFuse key block and fill in the board.EFUSE_KEY* HardwareKey +// objects. Call once at startup, before user code. Never raises. +void espressif_hardwarekey_init(void); + +// Fill in `key` for eFuse key block `slot`. Sets key->purpose to HMAC when the +// block is burned HMAC_UP (importing its PSA key), else HARDWAREKEY_PURPOSE_UNUSED. +// Never raises. Returns true when the slot ended up usable. +bool hardwarekey_efuse_slot_load(mp_int_t slot, hardwarekey_hardwarekey_obj_t *key); diff --git a/ports/espressif/common-hal/hardwarekey/board.h b/ports/espressif/common-hal/hardwarekey/board.h new file mode 100644 index 00000000000..0ac32fa140c --- /dev/null +++ b/ports/espressif/common-hal/hardwarekey/board.h @@ -0,0 +1,30 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#pragma once + +// board.EFUSE_KEY0 .. board.EFUSE_KEY5: one fixed entry per eFuse key block, +// injected into every board's globals table through CIRCUITPY_BOARD_HARDWARE_KEYS +// (see shared-bindings/board/__init__.h). Each points at a static HardwareKey the +// startup probe (espressif_hardwarekey_init) fills in -- so, like board pins, the +// names exist at compile time and the objects are ready before user code runs. + +#include "shared-module/hardwarekey/HardwareKey.h" + +// Every HMAC-capable Espressif chip (S2/S3/C3/C5/C6/H2/P4) has 6 eFuse key blocks. +// HardwareKey.c static-asserts this against EFUSE_BLK_KEY_MAX - EFUSE_BLK_KEY0; +// if a future chip differs, update this list (and the assert) to match. +#define HARDWAREKEY_EFUSE_SLOT_COUNT 6 + +extern hardwarekey_hardwarekey_obj_t hardwarekey_efuse_keys[HARDWAREKEY_EFUSE_SLOT_COUNT]; + +#define CIRCUITPY_BOARD_HARDWARE_KEYS \ + { MP_ROM_QSTR(MP_QSTR_EFUSE_KEY0), MP_ROM_PTR(&hardwarekey_efuse_keys[0]) }, \ + { MP_ROM_QSTR(MP_QSTR_EFUSE_KEY1), MP_ROM_PTR(&hardwarekey_efuse_keys[1]) }, \ + { MP_ROM_QSTR(MP_QSTR_EFUSE_KEY2), MP_ROM_PTR(&hardwarekey_efuse_keys[2]) }, \ + { MP_ROM_QSTR(MP_QSTR_EFUSE_KEY3), MP_ROM_PTR(&hardwarekey_efuse_keys[3]) }, \ + { MP_ROM_QSTR(MP_QSTR_EFUSE_KEY4), MP_ROM_PTR(&hardwarekey_efuse_keys[4]) }, \ + { MP_ROM_QSTR(MP_QSTR_EFUSE_KEY5), MP_ROM_PTR(&hardwarekey_efuse_keys[5]) }, diff --git a/ports/espressif/common-hal/microcontroller/Pin.h b/ports/espressif/common-hal/microcontroller/Pin.h index 7925ac9f8de..2132c65885e 100644 --- a/ports/espressif/common-hal/microcontroller/Pin.h +++ b/ports/espressif/common-hal/microcontroller/Pin.h @@ -28,6 +28,9 @@ extern void claim_pin_number(gpio_num_t pin_number); extern bool pin_number_is_free(gpio_num_t pin_number); extern void never_reset_pin_number(gpio_num_t pin_number); +extern uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); +extern void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin); + extern void preserve_pin_number(gpio_num_t pin_number); // Allow the board to reset a pin in a board-specific way. This can be used diff --git a/ports/espressif/common-hal/wifi/Monitor.c b/ports/espressif/common-hal/wifi/Monitor.c index 635ba7a5a67..f6d0e6e4d8f 100644 --- a/ports/espressif/common-hal/wifi/Monitor.c +++ b/ports/espressif/common-hal/wifi/Monitor.c @@ -9,6 +9,7 @@ #include "py/mpstate.h" #include "py/runtime.h" +#include "bindings/espidf/__init__.h" #include "shared-bindings/wifi/Monitor.h" #include "shared-bindings/wifi/Packet.h" @@ -57,9 +58,26 @@ static void wifi_monitor_cb(void *recv_buf, wifi_promiscuous_pkt_type_t type) { } } +// Tune the radio and return the channel it is on afterwards. Raises +// ValueError for a channel this radio cannot use. Any other failure, for +// example while the station is associated, leaves the radio where it was. +static uint8_t set_channel(uint8_t channel) { + esp_err_t result = esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + if (result == ESP_ERR_INVALID_ARG) { + mp_arg_error_invalid(MP_QSTR_channel); + } else if (result != ESP_OK) { + wifi_second_chan_t second; + CHECK_ESP_RESULT(esp_wifi_get_channel(&channel, &second)); + } + return channel; +} + void common_hal_wifi_monitor_construct(wifi_monitor_obj_t *self, uint8_t channel, size_t queue) { mp_rom_error_text_t monitor_mode_init_error = MP_ERROR_TEXT("monitor init failed"); + // Before anything needs undoing on failure. + channel = set_channel(channel); + self->queue = xQueueCreate(queue, sizeof(monitor_packet_t)); if (!self->queue) { mp_raise_RuntimeError(monitor_mode_init_error); @@ -74,7 +92,6 @@ void common_hal_wifi_monitor_construct(wifi_monitor_obj_t *self, uint8_t channel if (esp_wifi_set_promiscuous(true) != ESP_OK) { mp_raise_RuntimeError(monitor_mode_init_error); } - esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); self->channel = channel; self->queue_length = queue; @@ -105,8 +122,7 @@ void common_hal_wifi_monitor_deinit(wifi_monitor_obj_t *self) { } void common_hal_wifi_monitor_set_channel(wifi_monitor_obj_t *self, uint8_t channel) { - self->channel = channel; - esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + self->channel = set_channel(channel); } mp_obj_t common_hal_wifi_monitor_get_channel(wifi_monitor_obj_t *self) { diff --git a/ports/espressif/common-hal/wifi/Radio.c b/ports/espressif/common-hal/wifi/Radio.c index cc92a41705a..78ce251def4 100644 --- a/ports/espressif/common-hal/wifi/Radio.c +++ b/ports/espressif/common-hal/wifi/Radio.c @@ -242,6 +242,7 @@ void common_hal_wifi_radio_stop_station(wifi_radio_obj_t *self) { } void common_hal_wifi_radio_start_ap(wifi_radio_obj_t *self, uint8_t *ssid, size_t ssid_len, uint8_t *password, size_t password_len, uint8_t channel, uint32_t authmode, uint8_t max_connections) { + bool was_ap = self->ap_mode; set_mode_ap(self, true); uint8_t esp_authmode = 0; @@ -275,7 +276,18 @@ void common_hal_wifi_radio_start_ap(wifi_radio_obj_t *self, uint8_t *ssid, size_ config->ap.max_connection = max_connections; - esp_wifi_set_config(WIFI_IF_AP, config); + esp_err_t result = esp_wifi_set_config(WIFI_IF_AP, config); + if (result != ESP_OK) { + if (!was_ap) { + set_mode_ap(self, false); + } + // The IDF returns ESP_ERR_INVALID_ARG for a channel this radio or its + // country setting cannot use (wifi_ap_config_t.channel). + if (result == ESP_ERR_INVALID_ARG) { + mp_arg_error_invalid(MP_QSTR_channel); + } + raise_esp_error(result); + } // Wait a few ms for the AP to start. Empirically, this takes < 3ms on ESP32, and < 1ms on other chips. for (size_t ms = 0; ms < 10; ms++) { if (common_hal_wifi_radio_get_ap_active(self)) { diff --git a/ports/espressif/esp-idf-config/sdkconfig-native.defaults b/ports/espressif/esp-idf-config/sdkconfig-native.defaults new file mode 100644 index 00000000000..c3eed046a61 --- /dev/null +++ b/ports/espressif/esp-idf-config/sdkconfig-native.defaults @@ -0,0 +1,15 @@ +# +# Espressif IoT Development Framework Configuration +# +# +# Component config +# +# +# ESP System Settings +# +# CONFIG_ESP_SYSTEM_MEMPROT is not set +# end of ESP System Settings + +# end of Component config + +# end of Espressif IoT Development Framework Configuration diff --git a/ports/espressif/esp-protocols b/ports/espressif/esp-protocols index 2f492c02289..f1e5cb6868a 160000 --- a/ports/espressif/esp-protocols +++ b/ports/espressif/esp-protocols @@ -1 +1 @@ -Subproject commit 2f492c02289015ecbb36851dd1bd04e0eb0a9937 +Subproject commit f1e5cb6868aa83b6147f1ecc8894aa043392332f diff --git a/ports/espressif/mpconfigport.h b/ports/espressif/mpconfigport.h index f0106f26b2c..afcf4a9896a 100644 --- a/ports/espressif/mpconfigport.h +++ b/ports/espressif/mpconfigport.h @@ -74,3 +74,9 @@ extern portMUX_TYPE background_task_mutex; #ifndef CIRCUITPY_ESP32P4_SWAP_LSFS #define CIRCUITPY_ESP32P4_SWAP_LSFS (0) #endif + +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE +// Loaded native code is copied out of the GC heap into executable RAM. +void *esp_native_code_commit(void *buf, size_t len, void *reloc); +#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) esp_native_code_commit(buf, len, reloc) +#endif diff --git a/ports/espressif/mpconfigport.mk b/ports/espressif/mpconfigport.mk index ee28bba9918..48ab8d04580 100644 --- a/ports/espressif/mpconfigport.mk +++ b/ports/espressif/mpconfigport.mk @@ -52,6 +52,10 @@ CIRCUITPY_4MB_FLASH_LARGE_USER_FS_LAYOUT ?= 0 # Enable more features CIRCUITPY_FULL_BUILD ?= 1 +# The 24 KB stack has room for a larger display refresh buffer. +CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE ?= 2048 +CIRCUITPY_BUSIO_SPI_ASYNC ?= 1 + # If SSL is enabled, it's mbedtls CIRCUITPY_SSL_MBEDTLS = 1 @@ -83,6 +87,7 @@ CIRCUITPY_ESPIDF ?= 1 CIRCUITPY_ESPULP ?= 1 CIRCUITPY_FRAMEBUFFERIO ?= 1 CIRCUITPY_FREQUENCYIO ?= 1 +CIRCUITPY_HARDWAREKEY ?= 1 CIRCUITPY_HASHLIB ?= 1 CIRCUITPY_I2CTARGET = 0 CIRCUITPY_MAX3421E ?= 1 @@ -108,6 +113,9 @@ ifeq ($(IDF_TARGET),esp32) # Modules CIRCUITPY_RGBMATRIX = 0 +# No HMAC peripheral (introduced starting with ESP32-S2) +CIRCUITPY_HARDWAREKEY = 0 + # Has no USB CIRCUITPY_USB_DEVICE = 0 @@ -121,6 +129,9 @@ CIRCUITPY_ESPCAMERA = 0 CIRCUITPY_ESPULP = 0 CIRCUITPY_MEMORYMAP = 0 +# No HMAC peripheral (SOC_HMAC_SUPPORTED is not defined for this target) +CIRCUITPY_HARDWAREKEY = 0 + # No capacitive touch peripheral CIRCUITPY_ALARM_TOUCH = 0 CIRCUITPY_TOUCHIO_USE_NATIVE = 0 @@ -254,7 +265,7 @@ CIRCUITPY_SDIOIO = 0 CIRCUITPY_USB_DEVICE = 0 CIRCUITPY_ESP_USB_SERIAL_JTAG ?= 1 -#### esp32c6 ########################################################## +#### esp32c61 ######################################################### else ifeq ($(IDF_TARGET),esp32c61) # Modules CIRCUITPY_ESPCAMERA = 0 @@ -262,6 +273,9 @@ CIRCUITPY_ESPULP = 0 CIRCUITPY_MEMORYMAP = 0 CIRCUITPY_RGBMATRIX = 0 +# No HMAC peripheral (SOC_HMAC_SUPPORTED is not defined for this target) +CIRCUITPY_HARDWAREKEY = 0 + # No capacitive touch peripheral CIRCUITPY_ALARM_TOUCH = 0 CIRCUITPY_TOUCHIO_USE_NATIVE = 0 @@ -395,6 +409,9 @@ CIRCUITPY_AUDIOIO = 0 endif #### end chip-specific choices ######################################## +# By default, load native .mpy files. Boards without the flash space turn it off. +CIRCUITPY_LOAD_NATIVE ?= 1 + # By default, enable dualbank, and it'll be disabled for small flash sizes CIRCUITPY_DUALBANK ?= 1 diff --git a/ports/espressif/supervisor/internal_flash.c b/ports/espressif/supervisor/internal_flash.c index a18d3fe0a0f..4b3836188a1 100644 --- a/ports/espressif/supervisor/internal_flash.c +++ b/ports/espressif/supervisor/internal_flash.c @@ -43,8 +43,8 @@ static uint32_t _cache_lba = 0xffffffff; #define SECSIZE(fs) ((fs)->ssize) #endif // FF_MAX_SS == FF_MIN_SS static DWORD fatfs_bytes(void) { - fs_user_mount_t *fs_mount = filesystem_circuitpy(); - FATFS *fatfs = &fs_mount->fatfs; + supervisor_vfs_t *fs_mount = filesystem_circuitpy(); + FATFS *fatfs = &fs_mount->fat.fatfs; return (fatfs->csize * SECSIZE(fatfs)) * (fatfs->n_fatent - 2); } static bool storage_extended = true; diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index 34cfba32b7e..68d97c20dcf 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -15,6 +15,9 @@ #include "supervisor/shared/serial.h" #include "py/mpprint.h" #include "py/runtime.h" +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE +#include "py/persistentcode.h" +#endif #include "esp_mac.h" #include "freertos/FreeRTOS.h" @@ -28,6 +31,9 @@ #include "common-hal/busio/SPI.h" #include "common-hal/busio/UART.h" #include "common-hal/dualbank/__init__.h" +#if CIRCUITPY_HARDWAREKEY +#include "common-hal/hardwarekey/__init__.h" +#endif #include "common-hal/ps2io/Ps2.h" #include "common-hal/watchdog/WatchDogTimer.h" #include "common-hal/socketpool/Socket.h" @@ -87,6 +93,10 @@ #include "esp_rom_efuse.h" #include "esp_timer.h" +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE && defined(CONFIG_IDF_TARGET_ESP32S2) +#include "esp_memory_utils.h" +#endif + #ifdef CONFIG_IDF_TARGET_ESP32 #include "hal/efuse_hal.h" #include "esp32/rom/efuse.h" @@ -287,6 +297,12 @@ safe_mode_t port_init(void) { _never_reset_spi_ram_flash(); + #if CIRCUITPY_HARDWAREKEY + // Populate board.EFUSE_KEY* from the eFuse key blocks. eFuse reads and the + // PSA key import need no filesystem or VM, so this is safe here. + espressif_hardwarekey_init(); + #endif + esp_reset_reason_t reason = esp_reset_reason(); switch (reason) { case ESP_RST_BROWNOUT: @@ -347,6 +363,56 @@ size_t port_heap_get_largest_free_size(void) { return free_size; } +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE +// Loaded native code, kept outside the GC heap until port_gc_deinit(). +typedef struct _native_code_node_t { + struct _native_code_node_t *next; + uint32_t data[]; +} native_code_node_t; + +static native_code_node_t *native_code_head = NULL; + +void port_gc_deinit(void) { + while (native_code_head != NULL) { + native_code_node_t *next = native_code_head->next; + heap_caps_free(native_code_head); + native_code_head = next; + } +} + +// Copy `len` bytes of machine code from `buf` into executable memory and return +// the executable address, applying the relocations in `reloc` (if any) against +// that address first. Raises MemoryError when no executable memory is available. +void *esp_native_code_commit(void *buf, size_t len, void *reloc) { + len = (len + 3) & ~3; + size_t len_node = sizeof(native_code_node_t) + len; + native_code_node_t *node = heap_caps_malloc(len_node, MALLOC_CAP_EXEC); + #if defined(CONFIG_IDF_TARGET_ESP32S2) + // The S2 can hand out MALLOC_CAP_EXEC memory that the CPU cannot fetch from. + if (node != NULL && !esp_ptr_executable(node)) { + heap_caps_free(node); + node = NULL; + } + #endif + if (node == NULL) { + m_malloc_fail(len_node); + } + node->next = native_code_head; + native_code_head = node; + void *p = node->data; + if (reloc) { + mp_native_relocate(reloc, buf, (uintptr_t)p); + } + // Word copy: Xtensa executable RAM is not byte-addressable. + const uint32_t *src = buf; + uint32_t *dst = p; + for (size_t i = 0; i < len / 4; i++) { + dst[i] = src[i]; + } + return p; +} +#endif + void reset_port_early(void) { // esp-camera adds an I2C device on the ESP I2C bus, and keeps it there. This // is unlike busio.I2C, which adds and removes the device on each operation. diff --git a/ports/espressif/tools/check-sdkconfig.py b/ports/espressif/tools/check-sdkconfig.py index 12254a71d01..f16dc3c871a 100755 --- a/ports/espressif/tools/check-sdkconfig.py +++ b/ports/espressif/tools/check-sdkconfig.py @@ -34,6 +34,17 @@ def validate(sdk_config, circuitpy_config): f"{var} is incompatible with {partition_table=} (no ota_1 partition)" ) + # Native machine code executes from RAM; the PMS/PMP memory protection + # forbids that and makes MALLOC_CAP_EXEC allocations fail. + if ( + circuitpy_config.get("CIRCUITPY_ENABLE_MPY_NATIVE") + or circuitpy_config.get("CIRCUITPY_LOAD_NATIVE") + ) and sdk_config.get("CONFIG_ESP_SYSTEM_MEMPROT"): + raise SystemExit( + "CIRCUITPY_ENABLE_MPY_NATIVE=1 / CIRCUITPY_LOAD_NATIVE=1 require CONFIG_ESP_SYSTEM_MEMPROT=n " + "(see esp-idf-config/sdkconfig-native.defaults)" + ) + # Add more checks here for other things we want to verify. return diff --git a/ports/espressif/tools/update_sdkconfig.py b/ports/espressif/tools/update_sdkconfig.py index d46514873bd..f262a00e58b 100644 --- a/ports/espressif/tools/update_sdkconfig.py +++ b/ports/espressif/tools/update_sdkconfig.py @@ -164,6 +164,8 @@ def update(debug, board, update_all): # noqa: C901 too complex psram_size = "0" uf2_bootloader = None ble_enabled = None + load_native = True # Matches the mpconfigport.mk default. + mpy_native = False for line in board_make.read_text().split("\n"): if "=" not in line or line.startswith("#"): continue @@ -192,6 +194,10 @@ def update(debug, board, update_all): # noqa: C901 too complex uf2_bootloader = not (value == "0") elif key == "CIRCUITPY_BLEIO_NATIVE": ble_enabled = not (value == "0") + elif key == "CIRCUITPY_LOAD_NATIVE": + load_native = not (value == "0") + elif key == "CIRCUITPY_ENABLE_MPY_NATIVE": + mpy_native = value == "1" os.environ["IDF_TARGET"] = target os.environ["COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE"] = ( @@ -239,6 +245,9 @@ def update(debug, board, update_all): # noqa: C901 too complex if ble_enabled: ble_config = pathlib.Path("esp-idf-config/sdkconfig-ble.defaults") sdkconfigs.append(ble_config) + if load_native or mpy_native: + native_config = pathlib.Path("esp-idf-config/sdkconfig-native.defaults") + sdkconfigs.append(native_config) board_config = pathlib.Path(f"boards/{board}/sdkconfig") # Don't include the board file in cp defaults. The board may have custom # overrides. diff --git a/ports/litex/Makefile b/ports/litex/Makefile deleted file mode 100644 index 9792ce301a3..00000000000 --- a/ports/litex/Makefile +++ /dev/null @@ -1,138 +0,0 @@ -# This file is part of the CircuitPython project: https://circuitpython.org -# -# SPDX-FileCopyrightText: Copyright (c) 2019 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -include ../../py/circuitpy_mkenv.mk - -CROSS_COMPILE = riscv64-unknown-elf- - -####################################### -# CFLAGS -####################################### - -INC += -I. -INC += -I../.. -INC += -I$(BUILD) -INC += -I$(BUILD)/genhdr -INC += -I./boards -INC += -I./boards/$(BOARD) -INC += -I./peripherals -INC += -I../../lib/mp-readline -INC += -I../../lib/tinyusb/src -INC += -I../../supervisor/shared/usb - - -#Debugging/Optimization -ifeq ($(DEBUG), 1) - CFLAGS += -ggdb - # You may want to enable these flags to make setting breakpoints easier. - CFLAGS += -fno-inline -fno-ipa-sra - OPTIMIZATION_FLAGS ?= -Og -else - CFLAGS += -DNDEBUG -ggdb3 - OPTIMIZATION_FLAGS ?= -O2 -endif - -# option to override compiler optimization level, set in boards/$(BOARD)/mpconfigboard.mk -CFLAGS += $(OPTIMIZATION_FLAGS) - -CFLAGS += $(INC) -Werror -Wall -std=gnu11 -nostdlib -fshort-enums $(BASE_CFLAGS) $(C_DEFS) $(CFLAGS_MOD) $(COPT) -Werror=missing-prototypes -Werror=old-style-definition - -# TODO: check this -CFLAGS += -D__START=main -DFOMU - -LD_FILE := boards/$(BOARD)/$(BOARD)-spi.ld - -LDFLAGS = $(CFLAGS) -Wl,-nostdlib -Wl,-T,$(LD_FILE) -Wl,-Map=$@.map -Wl,-cref -Wl,-gc-sections -specs=nano.specs -Wl,-melf32lriscv -LIBS := -lgcc -lc - - -LDFLAGS += -ffreestanding -nostartfiles -Wl,--gc-section -Wl,-Bstatic -Wl,-melf32lriscv -nostartfiles \ - -Wl,--no-warn-mismatch \ - -Wl,--build-id=none - -# Use toolchain libm if we're not using our own. -ifndef INTERNAL_LIBM -LIBS += -lm -endif - -# TinyUSB defines -CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_VALENTYUSB_EPTRI -DCFG_TUD_CDC_RX_BUFSIZE=1024 -DCFG_TUD_CDC_TX_BUFSIZE=1024 -DCFG_TUD_MSC_BUFSIZE=4096 -DCFG_TUD_MIDI_RX_BUFSIZE=128 -DCFG_TUD_MIDI_TX_BUFSIZE=128 - - -###################################### -# source -###################################### - - -SRC_C += \ - background.c \ - mphalport.c \ - boards/$(BOARD)/board.c \ - boards/$(BOARD)/pins.c - -ifneq ($(CIRCUITPY_USB),0) -SRC_C += lib/tinyusb/src/portable/valentyusb/eptri/dcd_eptri.c -endif - -SRC_S_UPPER = \ - crt0-vexriscv.S - -SRC_S = shared/runtime/gchelper_rv32i.s - -SRC_C += shared/runtime/gchelper_native.c - -$(BUILD)/lib/tlsf/tlsf.o: CFLAGS += -Wno-cast-align - -ifneq ($(FROZEN_MPY_DIR),) -FROZEN_MPY_PY_FILES := $(shell find -L $(FROZEN_MPY_DIR) -type f -name '*.py') -FROZEN_MPY_MPY_FILES := $(addprefix $(BUILD)/,$(FROZEN_MPY_PY_FILES:.py=.mpy)) -endif - -OBJ += $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) -ifeq ($(INTERNAL_LIBM),1) -OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) -endif -OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S_UPPER:.S=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) - -$(BUILD)/$(FATFS_DIR)/ff.o: COPT += -Os -$(filter $(PY_BUILD)/../extmod/vfs_fat_%.o, $(PY_O)): COPT += -Os - -# List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(SRC_SUPERVISOR) $(SRC_MOD) $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED) $(SRC_CIRCUITPY_COMMON) -# Sources that only hold QSTRs after pre-processing. -SRC_QSTR_PREPROCESSOR += - - -all: $(BUILD)/firmware.bin $(BUILD)/firmware.dfu - -ifeq ($(VALID_BOARD),) -$(BUILD)/firmware.elf: invalid-board -else -$(BUILD)/firmware.elf: $(OBJ) - $(STEPECHO) "LINK $@" - $(Q)$(CC) -o $@ $(LDFLAGS) $^ -Wl,--print-memory-usage -Wl,--start-group $(LIBS) -Wl,--end-group - $(Q)$(SIZE) $@ | $(PYTHON) $(TOP)/tools/build_memory_info.py $@.map $(BUILD) -endif - -$(BUILD)/firmware.bin: $(BUILD)/firmware.elf - $(STEPECHO) "Create $@" - $(Q)$(OBJCOPY) -O binary $^ $@ -# $(Q)$(OBJCOPY) -O binary -j .vectors -j .text -j .data $^ $@ - -$(BUILD)/firmware.hex: $(BUILD)/firmware.elf - $(STEPECHO) "Create $@" - $(Q)$(OBJCOPY) -O ihex $^ $@ -# $(Q)$(OBJCOPY) -O ihex -j .vectors -j .text -j .data $^ $@ - -$(BUILD)/firmware.dfu: $(BUILD)/firmware.bin - $(ECHO) "Create $@" - $(PYTHON) $(TOP)/tools/dfu.py -b $^ -D 0x1209:0x5bf0 "$(BUILD)/firmware.dfu" - -include $(TOP)/py/mkrules.mk diff --git a/ports/litex/README.rst b/ports/litex/README.rst deleted file mode 100644 index bc1d20e6170..00000000000 --- a/ports/litex/README.rst +++ /dev/null @@ -1,25 +0,0 @@ -LiteX (FPGA) -============ - -`LiteX `_ is a Python-based System on a Chip (SoC) designer -for open source supported Field Programmable Gate Array (FPGA) chips. This means that the CPU -core(s) and peripherals are not defined by the physical chip. Instead, they are loaded as separate -"gateware". Once this gateware is loaded, CircuitPython can be loaded on top of it to work as -expected. - -Installation -------------- - -You'll need ``dfu-util`` to install CircuitPython on the Fomu. - -Make sure the foboot bootloader is updated. Instructions are here: https://github.com/im-tomu/fomu-workshop/blob/master/docs/bootloader.rst - -Once you've updated the bootloader, you should know how to use ``dfu-util``. It's pretty easy! - -To install CircuitPython do: - -.. code-block:: shell - - dfu-util -D adafruit-circuitpython-fomu-en_US-.dfu - -It will install and then restart. CIRCUITPY should appear as it usually does and work the same. diff --git a/ports/litex/background.c b/ports/litex/background.c deleted file mode 100644 index a09607bdef1..00000000000 --- a/ports/litex/background.c +++ /dev/null @@ -1,20 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/runtime.h" -#include "supervisor/filesystem.h" -#include "supervisor/usb.h" -#include "supervisor/shared/stack.h" -#include "supervisor/port.h" - -void port_background_task(void) { -} -void port_background_tick(void) { -} -void port_start_background_tick(void) { -} -void port_finish_background_tick(void) { -} diff --git a/ports/litex/background.h b/ports/litex/background.h deleted file mode 100644 index c2d98475678..00000000000 --- a/ports/litex/background.h +++ /dev/null @@ -1,12 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2019 Dan Halbert for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#ifndef MICROPY_INCLUDED_LITEX_BACKGROUND_H -#define MICROPY_INCLUDED_LITEX_BACKGROUND_H - -#endif // MICROPY_INCLUDED_LITEX_BACKGROUND_H diff --git a/ports/litex/boards/fomu/board.c b/ports/litex/boards/fomu/board.c deleted file mode 100644 index c54e877e633..00000000000 --- a/ports/litex/boards/fomu/board.c +++ /dev/null @@ -1,48 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/board.h" - -#include "mpconfigboard.h" -#include "csr.h" - -// ICE40 LED Driver hard macro. -// See http://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/IK/ICE40LEDDriverUsageGuide.ashx?document_id=50668 -enum led_registers { - LEDDCR0 = 8, - LEDDBR = 9, - LEDDONR = 10, - LEDDOFR = 11, - LEDDBCRR = 5, - LEDDBCFR = 6, - LEDDPWRR = 1, - LEDDPWRG = 2, - LEDDPWRB = 3, -}; - -#define BREATHE_ENABLE (1 << 7) -#define BREATHE_EDGE_ON (0 << 6) -#define BREATHE_EDGE_BOTH (1 << 6) -#define BREATHE_MODE_MODULATE (1 << 5) -#define BREATHE_RATE(x) ((x & 7) << 0) - -// Write a value into the LEDDA_IP register. -static void ledda_write(uint8_t value, uint8_t addr) { - rgb_addr_write(addr); - rgb_dat_write(value); -} - -void board_init(void) { - uint8_t onrate = 15; - uint8_t offrate = 1; - - ledda_write(BREATHE_ENABLE | BREATHE_MODE_MODULATE | BREATHE_RATE(onrate), LEDDBCRR); - ledda_write(BREATHE_ENABLE | BREATHE_MODE_MODULATE | BREATHE_RATE(offrate), LEDDBCFR); - - ledda_write(123, LEDDPWRR); // Red - ledda_write(3, LEDDPWRG); // Green - ledda_write(98, LEDDPWRB); // Blue -} diff --git a/ports/litex/boards/fomu/csr.h b/ports/litex/boards/fomu/csr.h deleted file mode 100644 index c2a2495f7b7..00000000000 --- a/ports/litex/boards/fomu/csr.h +++ /dev/null @@ -1,650 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -// -------------------------------------------------------------------------------- -// Auto-generated by Migen (f4fcd10) & LiteX (de205d4a) on 2019-11-25 14:57:34 -// -------------------------------------------------------------------------------- -#include -#pragma once -#include -#ifdef CSR_ACCESSORS_DEFINED -extern void csr_writeb(uint8_t value, unsigned long addr); -extern uint8_t csr_readb(unsigned long addr); -extern void csr_writew(uint16_t value, unsigned long addr); -extern uint16_t csr_readw(unsigned long addr); -extern void csr_writel(uint32_t value, unsigned long addr); -extern uint32_t csr_readl(unsigned long addr); -#else /* ! CSR_ACCESSORS_DEFINED */ -#include -#endif /* ! CSR_ACCESSORS_DEFINED */ - -/* ctrl */ -#define CSR_CTRL_BASE 0xe0000000L -#define CSR_CTRL_RESET_ADDR 0xe0000000L -#define CSR_CTRL_RESET_SIZE 1 -static inline unsigned char ctrl_reset_read(void) { - unsigned char r = csr_readl(0xe0000000L); - return r; -} -static inline void ctrl_reset_write(unsigned char value) { - csr_writel(value, 0xe0000000L); -} -#define CSR_CTRL_SCRATCH_ADDR 0xe0000004L -#define CSR_CTRL_SCRATCH_SIZE 4 -static inline unsigned int ctrl_scratch_read(void) { - unsigned int r = csr_readl(0xe0000004L); - r <<= 8; - r |= csr_readl(0xe0000008L); - r <<= 8; - r |= csr_readl(0xe000000cL); - r <<= 8; - r |= csr_readl(0xe0000010L); - return r; -} -static inline void ctrl_scratch_write(unsigned int value) { - csr_writel(value >> 24, 0xe0000004L); - csr_writel(value >> 16, 0xe0000008L); - csr_writel(value >> 8, 0xe000000cL); - csr_writel(value, 0xe0000010L); -} -#define CSR_CTRL_BUS_ERRORS_ADDR 0xe0000014L -#define CSR_CTRL_BUS_ERRORS_SIZE 4 -static inline unsigned int ctrl_bus_errors_read(void) { - unsigned int r = csr_readl(0xe0000014L); - r <<= 8; - r |= csr_readl(0xe0000018L); - r <<= 8; - r |= csr_readl(0xe000001cL); - r <<= 8; - r |= csr_readl(0xe0000020L); - return r; -} - -/* lxspi */ -#define CSR_LXSPI_BASE 0xe0007800L -#define CSR_LXSPI_BITBANG_ADDR 0xe0007800L -#define CSR_LXSPI_BITBANG_SIZE 1 -static inline unsigned char lxspi_bitbang_read(void) { - unsigned char r = csr_readl(0xe0007800L); - return r; -} -static inline void lxspi_bitbang_write(unsigned char value) { - csr_writel(value, 0xe0007800L); -} -#define CSR_LXSPI_BITBANG_MOSI_OFFSET 0 -#define CSR_LXSPI_BITBANG_MOSI_SIZE 1 -#define CSR_LXSPI_BITBANG_CLK_OFFSET 1 -#define CSR_LXSPI_BITBANG_CLK_SIZE 1 -#define CSR_LXSPI_BITBANG_CS_N_OFFSET 2 -#define CSR_LXSPI_BITBANG_CS_N_SIZE 1 -#define CSR_LXSPI_BITBANG_DIR_OFFSET 3 -#define CSR_LXSPI_BITBANG_DIR_SIZE 1 -#define CSR_LXSPI_MISO_ADDR 0xe0007804L -#define CSR_LXSPI_MISO_SIZE 1 -static inline unsigned char lxspi_miso_read(void) { - unsigned char r = csr_readl(0xe0007804L); - return r; -} -#define CSR_LXSPI_BITBANG_EN_ADDR 0xe0007808L -#define CSR_LXSPI_BITBANG_EN_SIZE 1 -static inline unsigned char lxspi_bitbang_en_read(void) { - unsigned char r = csr_readl(0xe0007808L); - return r; -} -static inline void lxspi_bitbang_en_write(unsigned char value) { - csr_writel(value, 0xe0007808L); -} - -/* messible */ -#define CSR_MESSIBLE_BASE 0xe0008000L -#define CSR_MESSIBLE_IN_ADDR 0xe0008000L -#define CSR_MESSIBLE_IN_SIZE 1 -static inline unsigned char messible_in_read(void) { - unsigned char r = csr_readl(0xe0008000L); - return r; -} -static inline void messible_in_write(unsigned char value) { - csr_writel(value, 0xe0008000L); -} -#define CSR_MESSIBLE_OUT_ADDR 0xe0008004L -#define CSR_MESSIBLE_OUT_SIZE 1 -static inline unsigned char messible_out_read(void) { - unsigned char r = csr_readl(0xe0008004L); - return r; -} -#define CSR_MESSIBLE_STATUS_ADDR 0xe0008008L -#define CSR_MESSIBLE_STATUS_SIZE 1 -static inline unsigned char messible_status_read(void) { - unsigned char r = csr_readl(0xe0008008L); - return r; -} -#define CSR_MESSIBLE_STATUS_FULL_OFFSET 0 -#define CSR_MESSIBLE_STATUS_FULL_SIZE 1 -#define CSR_MESSIBLE_STATUS_HAVE_OFFSET 1 -#define CSR_MESSIBLE_STATUS_HAVE_SIZE 1 - -/* reboot */ -#define CSR_REBOOT_BASE 0xe0006000L -#define CSR_REBOOT_CTRL_ADDR 0xe0006000L -#define CSR_REBOOT_CTRL_SIZE 1 -static inline unsigned char reboot_ctrl_read(void) { - unsigned char r = csr_readl(0xe0006000L); - return r; -} -static inline void reboot_ctrl_write(unsigned char value) { - csr_writel(value, 0xe0006000L); -} -#define CSR_REBOOT_CTRL_IMAGE_OFFSET 0 -#define CSR_REBOOT_CTRL_IMAGE_SIZE 2 -#define CSR_REBOOT_CTRL_KEY_OFFSET 2 -#define CSR_REBOOT_CTRL_KEY_SIZE 6 -#define CSR_REBOOT_ADDR_ADDR 0xe0006004L -#define CSR_REBOOT_ADDR_SIZE 4 -static inline unsigned int reboot_addr_read(void) { - unsigned int r = csr_readl(0xe0006004L); - r <<= 8; - r |= csr_readl(0xe0006008L); - r <<= 8; - r |= csr_readl(0xe000600cL); - r <<= 8; - r |= csr_readl(0xe0006010L); - return r; -} -static inline void reboot_addr_write(unsigned int value) { - csr_writel(value >> 24, 0xe0006004L); - csr_writel(value >> 16, 0xe0006008L); - csr_writel(value >> 8, 0xe000600cL); - csr_writel(value, 0xe0006010L); -} - -/* rgb */ -#define CSR_RGB_BASE 0xe0006800L -#define CSR_RGB_DAT_ADDR 0xe0006800L -#define CSR_RGB_DAT_SIZE 1 -static inline unsigned char rgb_dat_read(void) { - unsigned char r = csr_readl(0xe0006800L); - return r; -} -static inline void rgb_dat_write(unsigned char value) { - csr_writel(value, 0xe0006800L); -} -#define CSR_RGB_ADDR_ADDR 0xe0006804L -#define CSR_RGB_ADDR_SIZE 1 -static inline unsigned char rgb_addr_read(void) { - unsigned char r = csr_readl(0xe0006804L); - return r; -} -static inline void rgb_addr_write(unsigned char value) { - csr_writel(value, 0xe0006804L); -} -#define CSR_RGB_CTRL_ADDR 0xe0006808L -#define CSR_RGB_CTRL_SIZE 1 -static inline unsigned char rgb_ctrl_read(void) { - unsigned char r = csr_readl(0xe0006808L); - return r; -} -static inline void rgb_ctrl_write(unsigned char value) { - csr_writel(value, 0xe0006808L); -} -#define CSR_RGB_CTRL_EXE_OFFSET 0 -#define CSR_RGB_CTRL_EXE_SIZE 1 -#define CSR_RGB_CTRL_CURREN_OFFSET 1 -#define CSR_RGB_CTRL_CURREN_SIZE 1 -#define CSR_RGB_CTRL_RGBLEDEN_OFFSET 2 -#define CSR_RGB_CTRL_RGBLEDEN_SIZE 1 -#define CSR_RGB_CTRL_RRAW_OFFSET 3 -#define CSR_RGB_CTRL_RRAW_SIZE 1 -#define CSR_RGB_CTRL_GRAW_OFFSET 4 -#define CSR_RGB_CTRL_GRAW_SIZE 1 -#define CSR_RGB_CTRL_BRAW_OFFSET 5 -#define CSR_RGB_CTRL_BRAW_SIZE 1 -#define CSR_RGB_RAW_ADDR 0xe000680cL -#define CSR_RGB_RAW_SIZE 1 -static inline unsigned char rgb_raw_read(void) { - unsigned char r = csr_readl(0xe000680cL); - return r; -} -static inline void rgb_raw_write(unsigned char value) { - csr_writel(value, 0xe000680cL); -} -#define CSR_RGB_RAW_R_OFFSET 0 -#define CSR_RGB_RAW_R_SIZE 1 -#define CSR_RGB_RAW_G_OFFSET 1 -#define CSR_RGB_RAW_G_SIZE 1 -#define CSR_RGB_RAW_B_OFFSET 2 -#define CSR_RGB_RAW_B_SIZE 1 - -/* timer0 */ -#define CSR_TIMER0_BASE 0xe0002800L -#define CSR_TIMER0_LOAD_ADDR 0xe0002800L -#define CSR_TIMER0_LOAD_SIZE 4 -static inline unsigned int timer0_load_read(void) { - unsigned int r = csr_readl(0xe0002800L); - r <<= 8; - r |= csr_readl(0xe0002804L); - r <<= 8; - r |= csr_readl(0xe0002808L); - r <<= 8; - r |= csr_readl(0xe000280cL); - return r; -} -static inline void timer0_load_write(unsigned int value) { - csr_writel(value >> 24, 0xe0002800L); - csr_writel(value >> 16, 0xe0002804L); - csr_writel(value >> 8, 0xe0002808L); - csr_writel(value, 0xe000280cL); -} -#define CSR_TIMER0_RELOAD_ADDR 0xe0002810L -#define CSR_TIMER0_RELOAD_SIZE 4 -static inline unsigned int timer0_reload_read(void) { - unsigned int r = csr_readl(0xe0002810L); - r <<= 8; - r |= csr_readl(0xe0002814L); - r <<= 8; - r |= csr_readl(0xe0002818L); - r <<= 8; - r |= csr_readl(0xe000281cL); - return r; -} -static inline void timer0_reload_write(unsigned int value) { - csr_writel(value >> 24, 0xe0002810L); - csr_writel(value >> 16, 0xe0002814L); - csr_writel(value >> 8, 0xe0002818L); - csr_writel(value, 0xe000281cL); -} -#define CSR_TIMER0_EN_ADDR 0xe0002820L -#define CSR_TIMER0_EN_SIZE 1 -static inline unsigned char timer0_en_read(void) { - unsigned char r = csr_readl(0xe0002820L); - return r; -} -static inline void timer0_en_write(unsigned char value) { - csr_writel(value, 0xe0002820L); -} -#define CSR_TIMER0_UPDATE_VALUE_ADDR 0xe0002824L -#define CSR_TIMER0_UPDATE_VALUE_SIZE 1 -static inline unsigned char timer0_update_value_read(void) { - unsigned char r = csr_readl(0xe0002824L); - return r; -} -static inline void timer0_update_value_write(unsigned char value) { - csr_writel(value, 0xe0002824L); -} -#define CSR_TIMER0_VALUE_ADDR 0xe0002828L -#define CSR_TIMER0_VALUE_SIZE 4 -static inline unsigned int timer0_value_read(void) { - unsigned int r = csr_readl(0xe0002828L); - r <<= 8; - r |= csr_readl(0xe000282cL); - r <<= 8; - r |= csr_readl(0xe0002830L); - r <<= 8; - r |= csr_readl(0xe0002834L); - return r; -} -#define CSR_TIMER0_EV_STATUS_ADDR 0xe0002838L -#define CSR_TIMER0_EV_STATUS_SIZE 1 -static inline unsigned char timer0_ev_status_read(void) { - unsigned char r = csr_readl(0xe0002838L); - return r; -} -static inline void timer0_ev_status_write(unsigned char value) { - csr_writel(value, 0xe0002838L); -} -#define CSR_TIMER0_EV_PENDING_ADDR 0xe000283cL -#define CSR_TIMER0_EV_PENDING_SIZE 1 -static inline unsigned char timer0_ev_pending_read(void) { - unsigned char r = csr_readl(0xe000283cL); - return r; -} -static inline void timer0_ev_pending_write(unsigned char value) { - csr_writel(value, 0xe000283cL); -} -#define CSR_TIMER0_EV_ENABLE_ADDR 0xe0002840L -#define CSR_TIMER0_EV_ENABLE_SIZE 1 -static inline unsigned char timer0_ev_enable_read(void) { - unsigned char r = csr_readl(0xe0002840L); - return r; -} -static inline void timer0_ev_enable_write(unsigned char value) { - csr_writel(value, 0xe0002840L); -} - -/* touch */ -#define CSR_TOUCH_BASE 0xe0005800L -#define CSR_TOUCH_O_ADDR 0xe0005800L -#define CSR_TOUCH_O_SIZE 1 -static inline unsigned char touch_o_read(void) { - unsigned char r = csr_readl(0xe0005800L); - return r; -} -static inline void touch_o_write(unsigned char value) { - csr_writel(value, 0xe0005800L); -} -#define CSR_TOUCH_OE_ADDR 0xe0005804L -#define CSR_TOUCH_OE_SIZE 1 -static inline unsigned char touch_oe_read(void) { - unsigned char r = csr_readl(0xe0005804L); - return r; -} -static inline void touch_oe_write(unsigned char value) { - csr_writel(value, 0xe0005804L); -} -#define CSR_TOUCH_I_ADDR 0xe0005808L -#define CSR_TOUCH_I_SIZE 1 -static inline unsigned char touch_i_read(void) { - unsigned char r = csr_readl(0xe0005808L); - return r; -} - -/* usb */ -#define CSR_USB_BASE 0xe0004800L -#define CSR_USB_PULLUP_OUT_ADDR 0xe0004800L -#define CSR_USB_PULLUP_OUT_SIZE 1 -static inline unsigned char usb_pullup_out_read(void) { - unsigned char r = csr_readl(0xe0004800L); - return r; -} -static inline void usb_pullup_out_write(unsigned char value) { - csr_writel(value, 0xe0004800L); -} -#define CSR_USB_ADDRESS_ADDR 0xe0004804L -#define CSR_USB_ADDRESS_SIZE 1 -static inline unsigned char usb_address_read(void) { - unsigned char r = csr_readl(0xe0004804L); - return r; -} -static inline void usb_address_write(unsigned char value) { - csr_writel(value, 0xe0004804L); -} -#define CSR_USB_ADDRESS_ADDR_OFFSET 0 -#define CSR_USB_ADDRESS_ADDR_SIZE 7 -#define CSR_USB_NEXT_EV_ADDR 0xe0004808L -#define CSR_USB_NEXT_EV_SIZE 1 -static inline unsigned char usb_next_ev_read(void) { - unsigned char r = csr_readl(0xe0004808L); - return r; -} -#define CSR_USB_NEXT_EV_IN_OFFSET 0 -#define CSR_USB_NEXT_EV_IN_SIZE 1 -#define CSR_USB_NEXT_EV_OUT_OFFSET 1 -#define CSR_USB_NEXT_EV_OUT_SIZE 1 -#define CSR_USB_NEXT_EV_SETUP_OFFSET 2 -#define CSR_USB_NEXT_EV_SETUP_SIZE 1 -#define CSR_USB_NEXT_EV_RESET_OFFSET 3 -#define CSR_USB_NEXT_EV_RESET_SIZE 1 -#define CSR_USB_SETUP_DATA_ADDR 0xe000480cL -#define CSR_USB_SETUP_DATA_SIZE 1 -static inline unsigned char usb_setup_data_read(void) { - unsigned char r = csr_readl(0xe000480cL); - return r; -} -#define CSR_USB_SETUP_DATA_DATA_OFFSET 0 -#define CSR_USB_SETUP_DATA_DATA_SIZE 8 -#define CSR_USB_SETUP_CTRL_ADDR 0xe0004810L -#define CSR_USB_SETUP_CTRL_SIZE 1 -static inline unsigned char usb_setup_ctrl_read(void) { - unsigned char r = csr_readl(0xe0004810L); - return r; -} -static inline void usb_setup_ctrl_write(unsigned char value) { - csr_writel(value, 0xe0004810L); -} -#define CSR_USB_SETUP_CTRL_RESET_OFFSET 5 -#define CSR_USB_SETUP_CTRL_RESET_SIZE 1 -#define CSR_USB_SETUP_STATUS_ADDR 0xe0004814L -#define CSR_USB_SETUP_STATUS_SIZE 1 -static inline unsigned char usb_setup_status_read(void) { - unsigned char r = csr_readl(0xe0004814L); - return r; -} -#define CSR_USB_SETUP_STATUS_EPNO_OFFSET 0 -#define CSR_USB_SETUP_STATUS_EPNO_SIZE 4 -#define CSR_USB_SETUP_STATUS_HAVE_OFFSET 4 -#define CSR_USB_SETUP_STATUS_HAVE_SIZE 1 -#define CSR_USB_SETUP_STATUS_PEND_OFFSET 5 -#define CSR_USB_SETUP_STATUS_PEND_SIZE 1 -#define CSR_USB_SETUP_STATUS_IS_IN_OFFSET 6 -#define CSR_USB_SETUP_STATUS_IS_IN_SIZE 1 -#define CSR_USB_SETUP_STATUS_DATA_OFFSET 7 -#define CSR_USB_SETUP_STATUS_DATA_SIZE 1 -#define CSR_USB_SETUP_EV_STATUS_ADDR 0xe0004818L -#define CSR_USB_SETUP_EV_STATUS_SIZE 1 -static inline unsigned char usb_setup_ev_status_read(void) { - unsigned char r = csr_readl(0xe0004818L); - return r; -} -static inline void usb_setup_ev_status_write(unsigned char value) { - csr_writel(value, 0xe0004818L); -} -#define CSR_USB_SETUP_EV_PENDING_ADDR 0xe000481cL -#define CSR_USB_SETUP_EV_PENDING_SIZE 1 -static inline unsigned char usb_setup_ev_pending_read(void) { - unsigned char r = csr_readl(0xe000481cL); - return r; -} -static inline void usb_setup_ev_pending_write(unsigned char value) { - csr_writel(value, 0xe000481cL); -} -#define CSR_USB_SETUP_EV_ENABLE_ADDR 0xe0004820L -#define CSR_USB_SETUP_EV_ENABLE_SIZE 1 -static inline unsigned char usb_setup_ev_enable_read(void) { - unsigned char r = csr_readl(0xe0004820L); - return r; -} -static inline void usb_setup_ev_enable_write(unsigned char value) { - csr_writel(value, 0xe0004820L); -} -#define CSR_USB_IN_DATA_ADDR 0xe0004824L -#define CSR_USB_IN_DATA_SIZE 1 -static inline unsigned char usb_in_data_read(void) { - unsigned char r = csr_readl(0xe0004824L); - return r; -} -static inline void usb_in_data_write(unsigned char value) { - csr_writel(value, 0xe0004824L); -} -#define CSR_USB_IN_DATA_DATA_OFFSET 0 -#define CSR_USB_IN_DATA_DATA_SIZE 8 -#define CSR_USB_IN_CTRL_ADDR 0xe0004828L -#define CSR_USB_IN_CTRL_SIZE 1 -static inline unsigned char usb_in_ctrl_read(void) { - unsigned char r = csr_readl(0xe0004828L); - return r; -} -static inline void usb_in_ctrl_write(unsigned char value) { - csr_writel(value, 0xe0004828L); -} -#define CSR_USB_IN_CTRL_EPNO_OFFSET 0 -#define CSR_USB_IN_CTRL_EPNO_SIZE 4 -#define CSR_USB_IN_CTRL_RESET_OFFSET 5 -#define CSR_USB_IN_CTRL_RESET_SIZE 1 -#define CSR_USB_IN_CTRL_STALL_OFFSET 6 -#define CSR_USB_IN_CTRL_STALL_SIZE 1 -#define CSR_USB_IN_STATUS_ADDR 0xe000482cL -#define CSR_USB_IN_STATUS_SIZE 1 -static inline unsigned char usb_in_status_read(void) { - unsigned char r = csr_readl(0xe000482cL); - return r; -} -#define CSR_USB_IN_STATUS_IDLE_OFFSET 0 -#define CSR_USB_IN_STATUS_IDLE_SIZE 1 -#define CSR_USB_IN_STATUS_HAVE_OFFSET 4 -#define CSR_USB_IN_STATUS_HAVE_SIZE 1 -#define CSR_USB_IN_STATUS_PEND_OFFSET 5 -#define CSR_USB_IN_STATUS_PEND_SIZE 1 -#define CSR_USB_IN_EV_STATUS_ADDR 0xe0004830L -#define CSR_USB_IN_EV_STATUS_SIZE 1 -static inline unsigned char usb_in_ev_status_read(void) { - unsigned char r = csr_readl(0xe0004830L); - return r; -} -static inline void usb_in_ev_status_write(unsigned char value) { - csr_writel(value, 0xe0004830L); -} -#define CSR_USB_IN_EV_PENDING_ADDR 0xe0004834L -#define CSR_USB_IN_EV_PENDING_SIZE 1 -static inline unsigned char usb_in_ev_pending_read(void) { - unsigned char r = csr_readl(0xe0004834L); - return r; -} -static inline void usb_in_ev_pending_write(unsigned char value) { - csr_writel(value, 0xe0004834L); -} -#define CSR_USB_IN_EV_ENABLE_ADDR 0xe0004838L -#define CSR_USB_IN_EV_ENABLE_SIZE 1 -static inline unsigned char usb_in_ev_enable_read(void) { - unsigned char r = csr_readl(0xe0004838L); - return r; -} -static inline void usb_in_ev_enable_write(unsigned char value) { - csr_writel(value, 0xe0004838L); -} -#define CSR_USB_OUT_DATA_ADDR 0xe000483cL -#define CSR_USB_OUT_DATA_SIZE 1 -static inline unsigned char usb_out_data_read(void) { - unsigned char r = csr_readl(0xe000483cL); - return r; -} -#define CSR_USB_OUT_DATA_DATA_OFFSET 0 -#define CSR_USB_OUT_DATA_DATA_SIZE 8 -#define CSR_USB_OUT_CTRL_ADDR 0xe0004840L -#define CSR_USB_OUT_CTRL_SIZE 1 -static inline unsigned char usb_out_ctrl_read(void) { - unsigned char r = csr_readl(0xe0004840L); - return r; -} -static inline void usb_out_ctrl_write(unsigned char value) { - csr_writel(value, 0xe0004840L); -} -#define CSR_USB_OUT_CTRL_EPNO_OFFSET 0 -#define CSR_USB_OUT_CTRL_EPNO_SIZE 4 -#define CSR_USB_OUT_CTRL_ENABLE_OFFSET 4 -#define CSR_USB_OUT_CTRL_ENABLE_SIZE 1 -#define CSR_USB_OUT_CTRL_RESET_OFFSET 5 -#define CSR_USB_OUT_CTRL_RESET_SIZE 1 -#define CSR_USB_OUT_CTRL_STALL_OFFSET 6 -#define CSR_USB_OUT_CTRL_STALL_SIZE 1 -#define CSR_USB_OUT_STATUS_ADDR 0xe0004844L -#define CSR_USB_OUT_STATUS_SIZE 1 -static inline unsigned char usb_out_status_read(void) { - unsigned char r = csr_readl(0xe0004844L); - return r; -} -#define CSR_USB_OUT_STATUS_EPNO_OFFSET 0 -#define CSR_USB_OUT_STATUS_EPNO_SIZE 4 -#define CSR_USB_OUT_STATUS_HAVE_OFFSET 4 -#define CSR_USB_OUT_STATUS_HAVE_SIZE 1 -#define CSR_USB_OUT_STATUS_PEND_OFFSET 5 -#define CSR_USB_OUT_STATUS_PEND_SIZE 1 -#define CSR_USB_OUT_EV_STATUS_ADDR 0xe0004848L -#define CSR_USB_OUT_EV_STATUS_SIZE 1 -static inline unsigned char usb_out_ev_status_read(void) { - unsigned char r = csr_readl(0xe0004848L); - return r; -} -static inline void usb_out_ev_status_write(unsigned char value) { - csr_writel(value, 0xe0004848L); -} -#define CSR_USB_OUT_EV_PENDING_ADDR 0xe000484cL -#define CSR_USB_OUT_EV_PENDING_SIZE 1 -static inline unsigned char usb_out_ev_pending_read(void) { - unsigned char r = csr_readl(0xe000484cL); - return r; -} -static inline void usb_out_ev_pending_write(unsigned char value) { - csr_writel(value, 0xe000484cL); -} -#define CSR_USB_OUT_EV_ENABLE_ADDR 0xe0004850L -#define CSR_USB_OUT_EV_ENABLE_SIZE 1 -static inline unsigned char usb_out_ev_enable_read(void) { - unsigned char r = csr_readl(0xe0004850L); - return r; -} -static inline void usb_out_ev_enable_write(unsigned char value) { - csr_writel(value, 0xe0004850L); -} -#define CSR_USB_OUT_ENABLE_STATUS_ADDR 0xe0004854L -#define CSR_USB_OUT_ENABLE_STATUS_SIZE 1 -static inline unsigned char usb_out_enable_status_read(void) { - unsigned char r = csr_readl(0xe0004854L); - return r; -} -#define CSR_USB_OUT_STALL_STATUS_ADDR 0xe0004858L -#define CSR_USB_OUT_STALL_STATUS_SIZE 1 -static inline unsigned char usb_out_stall_status_read(void) { - unsigned char r = csr_readl(0xe0004858L); - return r; -} - -/* version */ -#define CSR_VERSION_BASE 0xe0007000L -#define CSR_VERSION_MAJOR_ADDR 0xe0007000L -#define CSR_VERSION_MAJOR_SIZE 1 -static inline unsigned char version_major_read(void) { - unsigned char r = csr_readl(0xe0007000L); - return r; -} -#define CSR_VERSION_MINOR_ADDR 0xe0007004L -#define CSR_VERSION_MINOR_SIZE 1 -static inline unsigned char version_minor_read(void) { - unsigned char r = csr_readl(0xe0007004L); - return r; -} -#define CSR_VERSION_REVISION_ADDR 0xe0007008L -#define CSR_VERSION_REVISION_SIZE 1 -static inline unsigned char version_revision_read(void) { - unsigned char r = csr_readl(0xe0007008L); - return r; -} -#define CSR_VERSION_GITREV_ADDR 0xe000700cL -#define CSR_VERSION_GITREV_SIZE 4 -static inline unsigned int version_gitrev_read(void) { - unsigned int r = csr_readl(0xe000700cL); - r <<= 8; - r |= csr_readl(0xe0007010L); - r <<= 8; - r |= csr_readl(0xe0007014L); - r <<= 8; - r |= csr_readl(0xe0007018L); - return r; -} -#define CSR_VERSION_GITEXTRA_ADDR 0xe000701cL -#define CSR_VERSION_GITEXTRA_SIZE 2 -static inline unsigned short int version_gitextra_read(void) { - unsigned short int r = csr_readl(0xe000701cL); - r <<= 8; - r |= csr_readl(0xe0007020L); - return r; -} -#define CSR_VERSION_DIRTY_ADDR 0xe0007024L -#define CSR_VERSION_DIRTY_SIZE 1 -static inline unsigned char version_dirty_read(void) { - unsigned char r = csr_readl(0xe0007024L); - return r; -} -#define CSR_VERSION_DIRTY_DIRTY_OFFSET 0 -#define CSR_VERSION_DIRTY_DIRTY_SIZE 1 -#define CSR_VERSION_MODEL_ADDR 0xe0007028L -#define CSR_VERSION_MODEL_SIZE 1 -static inline unsigned char version_model_read(void) { - unsigned char r = csr_readl(0xe0007028L); - return r; -} -#define CSR_VERSION_MODEL_MODEL_OFFSET 0 -#define CSR_VERSION_MODEL_MODEL_SIZE 8 -#define CSR_VERSION_SEED_ADDR 0xe000702cL -#define CSR_VERSION_SEED_SIZE 4 -static inline unsigned int version_seed_read(void) { - unsigned int r = csr_readl(0xe000702cL); - r <<= 8; - r |= csr_readl(0xe0007030L); - r <<= 8; - r |= csr_readl(0xe0007034L); - r <<= 8; - r |= csr_readl(0xe0007038L); - return r; -} diff --git a/ports/litex/boards/fomu/fomu-spi.ld b/ports/litex/boards/fomu/fomu-spi.ld deleted file mode 100644 index 33ba3db686b..00000000000 --- a/ports/litex/boards/fomu/fomu-spi.ld +++ /dev/null @@ -1,122 +0,0 @@ -/* - GNU linker script for Fomu -*/ - -ENTRY(_start) - -/* Specify the memory areas */ -MEMORY -{ - FLASH (rx) : ORIGIN = 0x20040000, LENGTH = 0x100000 /* entire flash, 1 MiB */ - FLASH_FIRMWARE (rx) : ORIGIN = 0x20040000, LENGTH = 0x100000 /* entire flash, 1 MiB */ - RAM (xrw) : ORIGIN = 0x10000000, LENGTH = 0x00020000 /* 128 KiB */ -} - -/* top end of the stack */ -_estack = ORIGIN(RAM) + LENGTH(RAM); - -/* define output sections */ -SECTIONS -{ - /* This is the initialized data section - The program executes knowing that the data is in the RAM - but the loader puts the initial values in the FLASH_FIRMWARE (inidata). - It is one task of the startup to copy the initial values from FLASH to RAM. */ - .data : AT ( _sidata ) - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */ - - *(.itcm.*) /* Instruction Tightly Coupled Memory */ - *(.dtcm_data.*) /* Data Tightly Coupled Memory */ - - *(.text.cmp_lfn) - *(.text.qstr_find_strn) - *(.text.dcd_edpt_xfer) - *(.text.pop_rule) - *(.text.ff_wtoupper) - *(.text.dir_find) - *(.text.push_rule) - *(.text.csr_writel) - *(.text.csr_readl) - *(.text.timer0_ev_pending_write) - *(.text.autoreload_tick) - *(.text.filesystem_tick) - *(.text.usb_background) - - *(.text.dcd_*) - *(.text.tud_control_*) - *(.text.tud_cdc_n_write_flush) - *(.text.tud_task) - *(.text.tu_edpt_dir) - *(.text.tu_fifo_empty) - *(.text.usbd_edpt_busy) - *(.text.usb_irq_handler) - *(.text.supervisor_tick) - *(.text.port_get_raw_ticks) - *(.text.__modsi3) - *(.text.__udivsi3) - *(.text.irq_getmask) - *(.text.irq_setmask) - *(.text.irq_pending) - *(.text._osal_q_lock) - *(.text.osal_queue_receive) - - *(.text.mp_obj_get_type) - *(.text.mp_parse) - *(.text.parse_compile_execute) - *(.text.mp_map_lookup) - *(.text.mp_execute_bytecode) /* Note: this function is 7kb */ - - *(.ramtext) /* .text* sections (code) */ - *(.ramtext*) /* .text* sections (code) */ - *(.data) /* .data sections */ - *(.data*) /* .data* sections */ - *(.sdata) /* .data sections */ - *(.sdata*) /* .data* sections */ - - . = ALIGN(4); - _edata = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */ - } >RAM - - /* The program code and other data goes into FLASH_FIRMWARE */ - .text : - { - . = ALIGN(4); - KEEP(*(.text.start)) /* isr vector table */ - *(.text) /* .text sections (code) */ - *(.text*) /* .text* sections (code) */ - *(.rodata) /* .rodata sections (constants, strings, etc.) */ - *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ - *(.srodata) /* .rodata sections (constants, strings, etc.) */ - *(.srodata*) /* .rodata* sections (constants, strings, etc.) */ - - . = ALIGN(4); - _etext = .; /* define a global symbol at end of code */ - _sidata = _etext; /* This is used by the startup in order to initialize the .data section */ - } >FLASH_FIRMWARE - - /* Uninitialized data section */ - .bss : - { - . = ALIGN(4); - _sbss = .; /* define a global symbol at bss start; used by startup code */ - *(.bss) - *(.bss*) - *(.dtcm_bss.*) /* Data Tightly Coupled Memory */ - *(.sbss) - *(.sbss*) - *(COMMON) - - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end; used by startup code */ - } >RAM - - - /* this is to define the start of the heap, and make sure we have a minimum size */ - .heap : - { - . = ALIGN(4); - _heap_start = .; /* define a global symbol at heap start */ - } >RAM -} diff --git a/ports/litex/boards/fomu/generated/soc.h b/ports/litex/boards/fomu/generated/soc.h deleted file mode 100644 index dc821bc6b4a..00000000000 --- a/ports/litex/boards/fomu/generated/soc.h +++ /dev/null @@ -1,61 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -// -------------------------------------------------------------------------------- -// Auto-generated by Migen (f4fcd10) & LiteX (de205d4a) on 2019-11-25 15:17:59 -// -------------------------------------------------------------------------------- -#pragma once -#define CONFIG_BITSTREAM_SYNC_HEADER1 2123999870 -static inline int config_bitstream_sync_header1_read(void) { - return 2123999870; -} -#define CONFIG_BITSTREAM_SYNC_HEADER2 2125109630 -static inline int config_bitstream_sync_header2_read(void) { - return 2125109630; -} -#define CONFIG_CLOCK_FREQUENCY 12000000 -static inline int config_clock_frequency_read(void) { - return 12000000; -} -#define CONFIG_CPU_RESET_ADDR 0 -static inline int config_cpu_reset_addr_read(void) { - return 0; -} -#define CONFIG_CPU_TYPE "VEXRISCV" -static inline const char *config_cpu_type_read(void) { - return "VEXRISCV"; -} -#define CONFIG_CPU_TYPE_VEXRISCV -#define CONFIG_CPU_VARIANT "MIN" -static inline const char *config_cpu_variant_read(void) { - return "MIN"; -} -#define CONFIG_CPU_VARIANT_MIN -#define CONFIG_CSR_ALIGNMENT 32 -static inline int config_csr_alignment_read(void) { - return 32; -} -#define CONFIG_CSR_DATA_WIDTH 8 -static inline int config_csr_data_width_read(void) { - return 8; -} -#define CONFIG_FOMU_REV "HACKER" -static inline const char *config_fomu_rev_read(void) { - return "HACKER"; -} -#define CONFIG_FOMU_REV_HACKER -#define CONFIG_SHADOW_BASE 2147483648 -static inline int config_shadow_base_read(void) { - return 2147483648; -} -#define TIMER0_INTERRUPT 2 -static inline int timer0_interrupt_read(void) { - return 2; -} -#define USB_INTERRUPT 3 -static inline int usb_interrupt_read(void) { - return 3; -} diff --git a/ports/litex/boards/fomu/mpconfigboard.h b/ports/litex/boards/fomu/mpconfigboard.h deleted file mode 100644 index 497809110ed..00000000000 --- a/ports/litex/boards/fomu/mpconfigboard.h +++ /dev/null @@ -1,18 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2019 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -// Micropython setup - -#define MICROPY_HW_BOARD_NAME "Fomu" -#define MICROPY_HW_MCU_NAME "VexRiscv" - -#define FLASH_SIZE (0x100000) -#define FLASH_PAGE_SIZE (0x1000) -#define FLASH_PARTITION_OFFSET_BYTES (1024 * 1024) - -#define BOARD_FLASH_SIZE (FLASH_SIZE) diff --git a/ports/litex/boards/fomu/mpconfigboard.mk b/ports/litex/boards/fomu/mpconfigboard.mk deleted file mode 100644 index c8de94e9c06..00000000000 --- a/ports/litex/boards/fomu/mpconfigboard.mk +++ /dev/null @@ -1,19 +0,0 @@ -USB_VID = 0x1209 -USB_PID = 0x5BF0 -USB_PRODUCT = "Fomu" -USB_MANUFACTURER = "Foosn" - -INTERNAL_FLASH_FILESYSTEM = 1 -LONGINT_IMPL = MPZ - -# The default queue depth of 16 overflows on release builds, -# so increase it to 32. -CFLAGS += -DCFG_TUD_TASK_QUEUE_SZ=32 - -# Fomu only implements rv32i -CFLAGS += -march=rv32i -mabi=ilp32 -LDFLAGS += -march=rv32i -mabi=ilp32 - -CIRCUITPY_NEOPIXEL_WRITE = 1 -CIRCUITPY_DIGITALIO = 1 -CIRCUITPY_MICROCONTROLLER = 1 diff --git a/ports/litex/boards/fomu/pins.c b/ports/litex/boards/fomu/pins.c deleted file mode 100644 index a462c31528f..00000000000 --- a/ports/litex/boards/fomu/pins.c +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -static const mp_rom_map_elem_t board_module_globals_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - - { MP_ROM_QSTR(MP_QSTR_TOUCH1), MP_ROM_PTR(&pin_TOUCH1) }, - { MP_ROM_QSTR(MP_QSTR_TOUCH2), MP_ROM_PTR(&pin_TOUCH2) }, - { MP_ROM_QSTR(MP_QSTR_TOUCH3), MP_ROM_PTR(&pin_TOUCH3) }, - { MP_ROM_QSTR(MP_QSTR_TOUCH4), MP_ROM_PTR(&pin_TOUCH4) }, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/litex/boards/fomu/profiling.gdb.txt b/ports/litex/boards/fomu/profiling.gdb.txt deleted file mode 100644 index 28faf45b73d..00000000000 --- a/ports/litex/boards/fomu/profiling.gdb.txt +++ /dev/null @@ -1,16 +0,0 @@ -set pagination 0 -set logging file profile.txt -set logging overwrite - -server define poor_profile -set $total = $arg0 -set $i = 0 - set logging on - while($i<$total) - set $i = $i + 1 - cont - p $pc - bt - end - set logging off -end diff --git a/ports/litex/common-hal/digitalio/DigitalInOut.c b/ports/litex/common-hal/digitalio/DigitalInOut.c deleted file mode 100644 index 30dd3f4addf..00000000000 --- a/ports/litex/common-hal/digitalio/DigitalInOut.c +++ /dev/null @@ -1,104 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Scott Shawcroft for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/digitalio/DigitalInOut.h" -#include "py/runtime.h" - -#include "csr.h" - -void common_hal_digitalio_digitalinout_never_reset( - digitalio_digitalinout_obj_t *self) { - (void)self; -} - -digitalinout_result_t common_hal_digitalio_digitalinout_construct( - digitalio_digitalinout_obj_t *self, const mcu_pin_obj_t *pin) { - - // claim_pin(pin); - self->pin = pin; - - return DIGITALINOUT_OK; -} - -bool common_hal_digitalio_digitalinout_deinited(digitalio_digitalinout_obj_t *self) { - return self->pin == NULL; -} - -void common_hal_digitalio_digitalinout_deinit(digitalio_digitalinout_obj_t *self) { - if (common_hal_digitalio_digitalinout_deinited(self)) { - return; - } - - // reset_pin_number(0, self->pin->number); - self->pin = NULL; -} - -digitalinout_result_t common_hal_digitalio_digitalinout_switch_to_input( - digitalio_digitalinout_obj_t *self, digitalio_pull_t pull) { - (void)pull; - touch_oe_write(touch_oe_read() & ~(1 << self->pin->number)); - return DIGITALINOUT_OK; -} - -digitalinout_result_t common_hal_digitalio_digitalinout_switch_to_output( - digitalio_digitalinout_obj_t *self, bool value, - digitalio_drive_mode_t drive_mode) { - (void)drive_mode; - common_hal_digitalio_digitalinout_set_value(self, value); - touch_oe_write(touch_oe_read() | (1 << self->pin->number)); - return DIGITALINOUT_OK; -} - -digitalio_direction_t common_hal_digitalio_digitalinout_get_direction( - digitalio_digitalinout_obj_t *self) { - - return (touch_oe_read() & (1 << self->pin->number)) - ? DIRECTION_OUTPUT : DIRECTION_INPUT; -} - -void common_hal_digitalio_digitalinout_set_value( - digitalio_digitalinout_obj_t *self, bool value) { - if (value) { - touch_o_write(touch_o_read() | (1 << self->pin->number)); - } else { - touch_o_write(touch_o_read() & ~(1 << self->pin->number)); - } -} - -bool common_hal_digitalio_digitalinout_get_value( - digitalio_digitalinout_obj_t *self) { - return !!(touch_i_read() & (1 << self->pin->number)); -} - -digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode( - digitalio_digitalinout_obj_t *self, - digitalio_drive_mode_t drive_mode) { - (void)self; - (void)drive_mode; - return DIGITALINOUT_OK; -} - -digitalio_drive_mode_t common_hal_digitalio_digitalinout_get_drive_mode( - digitalio_digitalinout_obj_t *self) { - if (common_hal_digitalio_digitalinout_get_direction(self) == DIRECTION_OUTPUT) { - return DRIVE_MODE_PUSH_PULL; - } else { - return DRIVE_MODE_OPEN_DRAIN; - } -} - -digitalinout_result_t common_hal_digitalio_digitalinout_set_pull( - digitalio_digitalinout_obj_t *self, digitalio_pull_t pull) { - (void)self; - (void)pull; - return DIGITALINOUT_OK; -} - -digitalio_pull_t common_hal_digitalio_digitalinout_get_pull( - digitalio_digitalinout_obj_t *self) { - return PULL_NONE; -} diff --git a/ports/litex/common-hal/digitalio/__init__.c b/ports/litex/common-hal/digitalio/__init__.c deleted file mode 100644 index fa222ed01f0..00000000000 --- a/ports/litex/common-hal/digitalio/__init__.c +++ /dev/null @@ -1,7 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -// No digitalio module functions. diff --git a/ports/litex/common-hal/microcontroller/Pin.c b/ports/litex/common-hal/microcontroller/Pin.c deleted file mode 100644 index bb3636c5b3f..00000000000 --- a/ports/litex/common-hal/microcontroller/Pin.c +++ /dev/null @@ -1,50 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2016 Scott Shawcroft for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/microcontroller/Pin.h" - -#include "py/mphal.h" - -static uint8_t claimed_pins[1]; - -void reset_all_pins(void) { - // TODO -} - -// Mark pin as free and return it to a quiescent state. -void reset_pin_number(uint8_t pin_port, uint8_t pin_number) { - if (pin_port == 0x0F) { - return; - } - - // Clear claimed bit. - claimed_pins[pin_port] &= ~(1 << pin_number); -} - -void common_hal_reset_pin(const mcu_pin_obj_t *pin) { - if (pin == NULL) { - return; - } - reset_pin_number(0, pin->number); -} - -void claim_pin(const mcu_pin_obj_t *pin) { - // Set bit in claimed_pins bitmask. - claimed_pins[0] |= 1 << pin->number; -} - -void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { - claim_pin(pin); -} - -bool pin_number_is_free(uint8_t pin_port, uint8_t pin_number) { - return !(claimed_pins[pin_port] & 1 << pin_number); -} - -bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { - return pin_number_is_free(0, pin->number); -} diff --git a/ports/litex/common-hal/microcontroller/Pin.h b/ports/litex/common-hal/microcontroller/Pin.h deleted file mode 100644 index 860a8c9ccad..00000000000 --- a/ports/litex/common-hal/microcontroller/Pin.h +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2016 Scott Shawcroft -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/mphal.h" -#include "py/obj.h" - - -typedef struct { - mp_obj_base_t base; - uint8_t number; -} mcu_pin_obj_t; - -#define PIN(p_number) \ - { \ - { &mcu_pin_type }, \ - .number = p_number \ - } - -extern const mcu_pin_obj_t pin_TOUCH1; -extern const mcu_pin_obj_t pin_TOUCH2; -extern const mcu_pin_obj_t pin_TOUCH3; -extern const mcu_pin_obj_t pin_TOUCH4; - -void reset_all_pins(void); -// reset_pin_number takes the pin number instead of the pointer so that objects don't -// need to store a full pointer. -void reset_pin_number(uint8_t pin_port, uint8_t pin_number); -void claim_pin(const mcu_pin_obj_t *pin); -bool pin_number_is_free(uint8_t pin_port, uint8_t pin_number); -void never_reset_pin_number(uint8_t pin_port, uint8_t pin_number); -// GPIO_TypeDef * pin_port(uint8_t pin_port); -uint16_t pin_mask(uint8_t pin_number); diff --git a/ports/litex/common-hal/microcontroller/Processor.c b/ports/litex/common-hal/microcontroller/Processor.c deleted file mode 100644 index 0d1c29b1ad0..00000000000 --- a/ports/litex/common-hal/microcontroller/Processor.c +++ /dev/null @@ -1,50 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Dan Halbert for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include -#include "py/runtime.h" - -#include "common-hal/microcontroller/Processor.h" -#include "shared-bindings/microcontroller/Processor.h" -#include "shared-bindings/microcontroller/ResetReason.h" - -#include "csr.h" -#include "generated/soc.h" - -float common_hal_mcu_processor_get_temperature(void) { - return NAN; -} - -float common_hal_mcu_processor_get_voltage(void) { - return NAN; -} - -uint32_t common_hal_mcu_processor_get_frequency(void) { - return CONFIG_CLOCK_FREQUENCY; -} - -void common_hal_mcu_processor_get_uid(uint8_t raw_id[]) { - raw_id[0] = csr_readl(CSR_VERSION_MAJOR_ADDR); - raw_id[1] = csr_readl(CSR_VERSION_MINOR_ADDR); - raw_id[2] = csr_readl(CSR_VERSION_REVISION_ADDR); - raw_id[3] = csr_readl(CSR_VERSION_GITREV_ADDR + 0); - raw_id[4] = csr_readl(CSR_VERSION_GITREV_ADDR + 4); - raw_id[5] = csr_readl(CSR_VERSION_GITREV_ADDR + 8); - raw_id[6] = csr_readl(CSR_VERSION_GITREV_ADDR + 12); - raw_id[7] = csr_readl(CSR_VERSION_GITEXTRA_ADDR + 0); - raw_id[8] = csr_readl(CSR_VERSION_GITEXTRA_ADDR + 4); - raw_id[9] = csr_readl(CSR_VERSION_DIRTY_ADDR); - raw_id[10] = csr_readl(CSR_VERSION_MODEL_ADDR); - raw_id[11] = csr_readl(CSR_VERSION_SEED_ADDR + 0); - raw_id[12] = csr_readl(CSR_VERSION_SEED_ADDR + 4); - raw_id[13] = csr_readl(CSR_VERSION_SEED_ADDR + 8); - raw_id[14] = csr_readl(CSR_VERSION_SEED_ADDR + 12); -} - -mcu_reset_reason_t common_hal_mcu_processor_get_reset_reason(void) { - return MCU_RESET_REASON_UNKNOWN; -} diff --git a/ports/litex/common-hal/microcontroller/Processor.h b/ports/litex/common-hal/microcontroller/Processor.h deleted file mode 100644 index 56da8367f8c..00000000000 --- a/ports/litex/common-hal/microcontroller/Processor.h +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Dan Halbert for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define COMMON_HAL_MCU_PROCESSOR_UID_LENGTH 15 - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; - // Stores no state currently. -} mcu_processor_obj_t; diff --git a/ports/litex/common-hal/microcontroller/__init__.c b/ports/litex/common-hal/microcontroller/__init__.c deleted file mode 100644 index 02f3372c392..00000000000 --- a/ports/litex/common-hal/microcontroller/__init__.c +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2016 Scott Shawcroft for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/mphal.h" -#include "py/obj.h" -#include "py/runtime.h" - -#include "common-hal/microcontroller/Pin.h" -#include "common-hal/microcontroller/Processor.h" - -#include "shared-bindings/microcontroller/__init__.h" -#include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/microcontroller/Processor.h" - -#include "supervisor/filesystem.h" -#include "supervisor/shared/safe_mode.h" - -#include "csr.h" -#include "irq.h" - -void common_hal_mcu_delay_us(uint32_t delay) { - // if (__get_PRIMASK() == 0x00000000) { - // //by default use ticks_ms - // uint32_t start = get_us(); - // while (get_us()-start < delay) { - // __asm__ __volatile__("nop"); - // } - // } else { - // //when SysTick is disabled, approximate with busy loop - // const uint32_t ucount = HAL_RCC_GetSysClockFreq() / 1000000 * delay / LOOP_TICKS; - // for (uint32_t count = 0; ++count <= ucount;) { - // } - // } -} - -volatile uint32_t nesting_count = 0; - -__attribute__((section(".ramtext"))) -void common_hal_mcu_disable_interrupts(void) { - if (nesting_count == 0) { - irq_setie(0); - } - nesting_count++; -} - -__attribute__((section(".ramtext"))) -void common_hal_mcu_enable_interrupts(void) { - if (nesting_count == 0) { - // This is very very bad because it means there was mismatched disable/enables. - reset_into_safe_mode(SAFE_MODE_INTERRUPT_ERROR); - } - nesting_count--; - if (nesting_count > 0) { - return; - } - irq_setie(1); -} - -void common_hal_mcu_on_next_reset(mcu_runmode_t runmode) { - if (runmode == RUNMODE_SAFE_MODE) { - safe_mode_on_next_reset(SAFE_MODE_PROGRAMMATIC); - } -} - -void common_hal_mcu_reset(void) { - filesystem_flush(); // TODO: implement as part of flash improvements - // NVIC_SystemReset(); - while (1) { - ; - } -} - -// The singleton microcontroller.Processor object, bound to microcontroller.cpu -// It currently only has properties, and no state. -const mcu_processor_obj_t common_hal_mcu_processor_obj = { - .base = { - .type = &mcu_processor_type, - }, -}; - -const mcu_pin_obj_t pin_TOUCH1 = PIN(0); -const mcu_pin_obj_t pin_TOUCH2 = PIN(1); -const mcu_pin_obj_t pin_TOUCH3 = PIN(2); -const mcu_pin_obj_t pin_TOUCH4 = PIN(3); - -static const mp_rom_map_elem_t mcu_pin_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR_TOUCH1), MP_ROM_PTR(&pin_TOUCH1) }, - { MP_ROM_QSTR(MP_QSTR_TOUCH2), MP_ROM_PTR(&pin_TOUCH2) }, - { MP_ROM_QSTR(MP_QSTR_TOUCH3), MP_ROM_PTR(&pin_TOUCH3) }, - { MP_ROM_QSTR(MP_QSTR_TOUCH4), MP_ROM_PTR(&pin_TOUCH4) }, -}; -MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_globals_table); diff --git a/ports/litex/common-hal/neopixel_write/__init__.c b/ports/litex/common-hal/neopixel_write/__init__.c deleted file mode 100644 index 72750872b56..00000000000 --- a/ports/litex/common-hal/neopixel_write/__init__.c +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2018 hathach for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/mphal.h" -#include "shared-bindings/neopixel_write/__init__.h" -#include "csr.h" - -// ICE40 LED Driver hard macro. -// See http://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/IK/ICE40LEDDriverUsageGuide.ashx?document_id=50668 -enum led_registers { - LEDDCR0 = 8, - LEDDBR = 9, - LEDDONR = 10, - LEDDOFR = 11, - LEDDBCRR = 5, - LEDDBCFR = 6, - LEDDPWRR = 1, - LEDDPWRG = 2, - LEDDPWRB = 3, -}; - -// Control register definitions -#define LEDDCR0_LEDDEN (1 << 7) -#define LEDDCR0_FR250 (1 << 6) -#define LEDDCR0_OUTPOL (1 << 5) -#define LEDDCR0_OUTSKEW (1 << 4) -#define LEDDCR0_QUICKSTOP (1 << 3) -#define LEDDCR0_PWM_MODE (1 << 2) -#define LEDDCR0_BRMSBEXT (1 << 0) - -// Write a value into the LEDDA_IP register. -static void ledda_write(uint8_t value, uint8_t addr) { - rgb_addr_write(addr); - rgb_dat_write(value); -} - -static int ledda_init_done; - -static void ledda_init(void) { - if (ledda_init_done) { - return; - } - - // Enable the driver - rgb_ctrl_write((1 << CSR_RGB_CTRL_EXE_OFFSET) | (1 << CSR_RGB_CTRL_CURREN_OFFSET) | (1 << CSR_RGB_CTRL_RGBLEDEN_OFFSET)); - - ledda_write(LEDDCR0_LEDDEN | LEDDCR0_FR250 | LEDDCR0_QUICKSTOP, LEDDCR0); - - // Set clock register to 12 MHz / 64 kHz - 1 - ledda_write((12000000 / 64000) - 1, LEDDBR); - - // Ensure LED "breathe" effect is disabled - ledda_write(0, LEDDBCRR); - ledda_write(0, LEDDBCFR); - - // Also disable the LED blink time - ledda_write(0, LEDDONR); - ledda_write(0, LEDDOFR); - - ledda_init_done = 1; -} - -void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint8_t *pixels, uint32_t numBytes) { - (void)digitalinout; - (void)numBytes; - ledda_init(); - - ledda_write(pixels[0], LEDDPWRR); // Red - ledda_write(pixels[1], LEDDPWRG); // Green - ledda_write(pixels[2], LEDDPWRB); // Blue -} diff --git a/ports/litex/common-hal/os/__init__.c b/ports/litex/common-hal/os/__init__.c deleted file mode 100644 index 659411187fa..00000000000 --- a/ports/litex/common-hal/os/__init__.c +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2020 Sean Cross -// -// SPDX-License-Identifier: MIT - -#include "genhdr/mpversion.h" -#include "py/mpconfig.h" -#include "py/objstr.h" -#include "py/objtuple.h" -#include "py/qstr.h" - -#include "shared-bindings/os/__init__.h" - -bool common_hal_os_urandom(uint8_t *buffer, mp_uint_t length) { - return false; -} diff --git a/ports/litex/common-hal/time/__init__.c b/ports/litex/common-hal/time/__init__.c deleted file mode 100644 index 96283d79259..00000000000 --- a/ports/litex/common-hal/time/__init__.c +++ /dev/null @@ -1,25 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2016 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/mphal.h" - -#include "tick.h" - -uint64_t common_hal_time_monotonic_ms(void) { - return supervisor_ticks_ms64(); -} - -uint64_t common_hal_time_monotonic_ns(void) { - uint64_t ms; - uint32_t us_until_ms; - current_tick(&ms, &us_until_ms); - // us counts down. - return 1000 * (ms * 1000 + (1000 - us_until_ms)); -} - -void common_hal_time_delay_ms(uint32_t delay) { - mp_hal_delay_ms(delay); -} diff --git a/ports/litex/crt0-vexriscv.S b/ports/litex/crt0-vexriscv.S deleted file mode 100644 index 0517711865c..00000000000 --- a/ports/litex/crt0-vexriscv.S +++ /dev/null @@ -1,94 +0,0 @@ -.global main -.global isr - -.section .text.start -.global _start - -_start: - j crt_init - # This sentinel ensures that this program is loaded - # to RAM when loaded using dfu-util. - #.word 0x17ab0f23 - #.word 0x10001000 - -.section .ramtext -.global trap_entry -.align 4 -trap_entry: - sw x1, - 1*4(sp) - sw x5, - 2*4(sp) - sw x6, - 3*4(sp) - sw x7, - 4*4(sp) - sw x10, - 5*4(sp) - sw x11, - 6*4(sp) - sw x12, - 7*4(sp) - sw x13, - 8*4(sp) - sw x14, - 9*4(sp) - sw x15, -10*4(sp) - sw x16, -11*4(sp) - sw x17, -12*4(sp) - sw x28, -13*4(sp) - sw x29, -14*4(sp) - sw x30, -15*4(sp) - sw x31, -16*4(sp) - addi sp,sp,-16*4 - call isr - lw x1 , 15*4(sp) - lw x5, 14*4(sp) - lw x6, 13*4(sp) - lw x7, 12*4(sp) - lw x10, 11*4(sp) - lw x11, 10*4(sp) - lw x12, 9*4(sp) - lw x13, 8*4(sp) - lw x14, 7*4(sp) - lw x15, 6*4(sp) - lw x16, 5*4(sp) - lw x17, 4*4(sp) - lw x28, 3*4(sp) - lw x29, 2*4(sp) - lw x30, 1*4(sp) - lw x31, 0*4(sp) - addi sp,sp,16*4 - mret - -.text - -crt_init: - # # Flush the caches - # .word 16399 - # .word 19 - # .word 19 - # .word 19 - la sp, _estack - 4 - la a0, trap_entry - csrw mtvec, a0 - -bss_init: - la a0, _sbss - la a1, _ebss -bss_loop: - beq a0,a1,bss_done - sw zero,0(a0) - add a0,a0,4 - j bss_loop -bss_done: - - /* Load DATA */ - la t0, _sidata - la t1, _sdata - la t2, _edata -3: - lw t3, 0(t0) - sw t3, 0(t1) - /* _edata is aligned to 16 bytes. Use word-xfers. */ - addi t0, t0, 4 - addi t1, t1, 4 - bltu t1, t2, 3b - - li a0, 0x880 //880 enable timer + external interrupt sources (until mstatus.MIE is set, they will never trigger an interrupt) - csrw mie,a0 - - call main -infinite_loop: - j infinite_loop diff --git a/ports/litex/hw/common.h b/ports/litex/hw/common.h deleted file mode 100644 index 5597a866c62..00000000000 --- a/ports/litex/hw/common.h +++ /dev/null @@ -1,31 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -#pragma once -#include -static inline void csr_writeb(uint8_t value, uint32_t addr) { - *((volatile uint8_t *)addr) = value; -} - -static inline uint8_t csr_readb(uint32_t addr) { - return *(volatile uint8_t *)addr; -} - -static inline void csr_writew(uint16_t value, uint32_t addr) { - *((volatile uint16_t *)addr) = value; -} - -static inline uint16_t csr_readw(uint32_t addr) { - return *(volatile uint16_t *)addr; -} - -static inline void csr_writel(uint32_t value, uint32_t addr) { - *((volatile uint32_t *)addr) = value; -} - -static inline uint32_t csr_readl(uint32_t addr) { - return *(volatile uint32_t *)addr; -} diff --git a/ports/litex/irq.h b/ports/litex/irq.h deleted file mode 100644 index f0e0ee2ea4e..00000000000 --- a/ports/litex/irq.h +++ /dev/null @@ -1,73 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - - -#define CSR_MSTATUS_MIE 0x8 - -#define CSR_IRQ_MASK 0xBC0 -#define CSR_IRQ_PENDING 0xFC0 - -#define CSR_DCACHE_INFO 0xCC0 - -#define csrr(reg) ({ unsigned long __tmp; \ - asm volatile ("csrr %0, " #reg : "=r" (__tmp)); \ - __tmp; }) - -#define csrw(reg, val) ({ \ - if (__builtin_constant_p(val) && (unsigned long)(val) < 32) \ - asm volatile ("csrw " #reg ", %0" : : "i" (val)); \ - else \ - asm volatile ("csrw " #reg ", %0" : : "r" (val)); }) - -#define csrs(reg, bit) ({ \ - if (__builtin_constant_p(bit) && (unsigned long)(bit) < 32) \ - asm volatile ("csrrs x0, " #reg ", %0" : : "i" (bit)); \ - else \ - asm volatile ("csrrs x0, " #reg ", %0" : : "r" (bit)); }) - -#define csrc(reg, bit) ({ \ - if (__builtin_constant_p(bit) && (unsigned long)(bit) < 32) \ - asm volatile ("csrrc x0, " #reg ", %0" : : "i" (bit)); \ - else \ - asm volatile ("csrrc x0, " #reg ", %0" : : "r" (bit)); }) - -static inline unsigned int irq_getie(void) { - return (csrr(mstatus) & CSR_MSTATUS_MIE) != 0; -} - -static inline void irq_setie(unsigned int ie) { - if (ie) { - csrs(mstatus, CSR_MSTATUS_MIE); - } else { - csrc(mstatus, CSR_MSTATUS_MIE); - } -} - -static inline unsigned int irq_getmask(void) { - unsigned int mask; - asm volatile ("csrr %0, %1" : "=r" (mask) : "i" (CSR_IRQ_MASK)); - return mask; -} - -static inline void irq_setmask(unsigned int mask) { - asm volatile ("csrw %0, %1" : : "i" (CSR_IRQ_MASK), "r" (mask)); -} - -static inline unsigned int irq_pending(void) { - unsigned int pending; - asm volatile ("csrr %0, %1" : "=r" (pending) : "i" (CSR_IRQ_PENDING)); - return pending; -} - -#ifdef __cplusplus -} -#endif diff --git a/ports/litex/mpconfigport.h b/ports/litex/mpconfigport.h deleted file mode 100644 index 7ac34a44336..00000000000 --- a/ports/litex/mpconfigport.h +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2015 Glenn Ruben Bakke -// SPDX-FileCopyrightText: Copyright (c) 2019 Dan Halbert for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define CIRCUITPY_INTERNAL_NVM_SIZE (0) -#define MICROPY_NLR_THUMB (0) - -#include "py/circuitpy_mpconfig.h" - -#define MICROPY_NLR_SETJMP (1) -#define CIRCUITPY_DEFAULT_STACK_SIZE 0x6000 diff --git a/ports/litex/mpconfigport.mk b/ports/litex/mpconfigport.mk deleted file mode 100644 index a93a3890bbc..00000000000 --- a/ports/litex/mpconfigport.mk +++ /dev/null @@ -1,34 +0,0 @@ -# Internal math library is substantially smaller than toolchain one -INTERNAL_LIBM = 1 - -# Number of USB endpoint pairs. -USB_NUM_ENDPOINT_PAIRS = 16 - -# Longints can be implemented as mpz, as longlong, or not -LONGINT_IMPL = MPZ - -CIRCUITPY_ANALOGIO = 0 -CIRCUITPY_AUDIOBUSIO = 0 -CIRCUITPY_AUDIOIO = 0 -CIRCUITPY_BITBANGIO = 0 -CIRCUITPY_BLEIO_HCI = 0 -CIRCUITPY_BOARD = 0 -CIRCUITPY_BUSDEVICE = 0 -CIRCUITPY_BUSIO = 0 -CIRCUITPY_COUNTIO = 0 -CIRCUITPY_DISPLAYIO = 0 -CIRCUITPY_FREQUENCYIO = 0 -CIRCUITPY_I2CTARGET = 0 -CIRCUITPY_NVM = 0 -CIRCUITPY_PWMIO = 0 -CIRCUITPY_PULSEIO = 0 -CIRCUITPY_PWMIO = 0 -CIRCUITPY_ROTARYIO = 0 -CIRCUITPY_RTC = 0 -CIRCUITPY_SDCARDIO = 0 -# Enable USB support -CIRCUITPY_USB_HID = 1 -CIRCUITPY_USB_MIDI = 1 - -CIRCUITPY_BUILD_EXTENSIONS ?= dfu -CIRCUITPY_MIN_GCC_VERSION ?= 8 diff --git a/ports/litex/mphalport.c b/ports/litex/mphalport.c deleted file mode 100644 index b94699e81e7..00000000000 --- a/ports/litex/mphalport.c +++ /dev/null @@ -1,56 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2015 Glenn Ruben Bakke -// SPDX-FileCopyrightText: Copyright (c) 2018 Artur Pacholec -// -// SPDX-License-Identifier: MIT - -#include - -#include "lib/tinyusb/src/device/usbd.h" -#include "py/mphal.h" -#include "py/mpstate.h" -#include "py/gc.h" -#include "supervisor/cpu.h" -#include "supervisor/usb.h" - -#include "csr.h" -#include "generated/soc.h" - -#include "irq.h" - -void mp_hal_delay_us(mp_uint_t delay) { - mp_hal_delay_ms(delay / 1000); -} - -extern void SysTick_Handler(void); - -// This value contains the number of times "common_hal_mcu_disable_interrupts()" -// has been called without calling "common_hal_mcu_enable_interrupts()". Since -// this is the interrupt handler, that means we're handling an interrupt, so -// this value should be `0`. -// -// Interrupts should already be disabled when this handler is running, which means -// this value is logically already `1`. If we didn't do this, then interrupts would -// be prematurely enabled by interrupt handlers that enable and disable interrupts. -extern volatile uint32_t nesting_count; - -void isr(void); -__attribute__((section(".ramtext"))) -void isr(void) { - uint8_t irqs = irq_pending() & irq_getmask(); - - // Increase the "nesting count". Note: This should be going from 0 -> 1. - nesting_count += 1; - #ifdef CFG_TUSB_MCU - if (irqs & (1 << USB_INTERRUPT)) { - usb_irq_handler(0); - } - #endif - if (irqs & (1 << TIMER0_INTERRUPT)) { - SysTick_Handler(); - } - - // Decrease the "nesting count". Note: This should be going from 1 -> 0. - nesting_count -= 1; -} diff --git a/ports/litex/mphalport.h b/ports/litex/mphalport.h deleted file mode 100644 index fc019b2f42b..00000000000 --- a/ports/litex/mphalport.h +++ /dev/null @@ -1,19 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2015 Glenn Ruben Bakke -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include -#include - -#include "shared/runtime/interrupt_char.h" -#include "py/mpconfig.h" -#include "supervisor/shared/tick.h" - -#define mp_hal_ticks_ms() ((mp_uint_t)supervisor_ticks_ms32()) -// #define mp_hal_delay_us(us) NRFX_DELAY_US((uint32_t) (us)) - -bool mp_hal_stdin_any(void); diff --git a/ports/litex/qstrdefsport.h b/ports/litex/qstrdefsport.h deleted file mode 100644 index 2d2c2609234..00000000000 --- a/ports/litex/qstrdefsport.h +++ /dev/null @@ -1,10 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -// Do not use #pragma once in this file; it will cause a warning during qstr generation. - -// qstrs specific to this port -// *FORMAT-OFF* diff --git a/ports/litex/supervisor/internal_flash.c b/ports/litex/supervisor/internal_flash.c deleted file mode 100644 index ba21b91b872..00000000000 --- a/ports/litex/supervisor/internal_flash.c +++ /dev/null @@ -1,339 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2013, 2014 Damien P. George -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries -// -// SPDX-License-Identifier: MIT -#include "supervisor/internal_flash.h" - -#include -#include -#include - -#include "extmod/vfs.h" -#include "extmod/vfs_fat.h" -#include "py/mphal.h" -#include "py/obj.h" -#include "py/runtime.h" -#include "lib/oofatfs/ff.h" - -#include "supervisor/flash.h" -#include "supervisor/usb.h" - -#include "csr.h" -#include "irq.h" - -enum pin { - PIN_MOSI = 0, - PIN_CLK = 1, - PIN_CS = 2, - PIN_MISO_EN = 3, - PIN_MISO = 4, // Value is ignored -}; - -#define NO_CACHE 0xffffffff - -static uint8_t _flash_cache[FLASH_PAGE_SIZE] __attribute__((aligned(4))); -static uint32_t _flash_page_addr = NO_CACHE; -static bool _flash_cache_dirty; -// ------------------------------------------------------------------------- -// When performing SPI operations, the flash cannot be accessed. Since we -// normally execute directly from SPI, this can cause problems. -// To work around this, we execute from RAM. This is accomplished by marking -// functions as being in the section ".ramtext". -// When building under GCC with -O0 or -Od, the `inline` attribute is ignored. -// Therefore, we must re-implement these functions here and explicitly mark -// them as being in `.ramtext`, even though they really ought to be inlined. -__attribute__((section(".ramtext"))) -static inline void spi_writel(uint32_t value, uint32_t addr) { - *((volatile uint32_t *)addr) = value; -} - -__attribute__((section(".ramtext"))) -static inline uint32_t spi_readl(uint32_t addr) { - return *(volatile uint32_t *)addr; -} - -__attribute__((section(".ramtext"))) -static inline void bb_spi_write(unsigned char value) { - spi_writel(value, CSR_LXSPI_BITBANG_ADDR); -} - -__attribute__((section(".ramtext"))) -static inline uint32_t bb_read(void) { - return spi_readl(CSR_LXSPI_MISO_ADDR); -} - -__attribute__((section(".ramtext"))) -static inline void bb_spi_en(unsigned int en) { - spi_writel(en, CSR_LXSPI_BITBANG_EN_ADDR); -} - -__attribute__((section(".ramtext"))) -static inline void bb_spi_irq_setie(unsigned int ie) { - if (ie) { - csrs(mstatus, CSR_MSTATUS_MIE); - } else { - csrc(mstatus, CSR_MSTATUS_MIE); - } -} - -__attribute__((section(".ramtext"))) -static inline void bb_spi_begin(void) { - bb_spi_write((0 << PIN_CLK) | (0 << PIN_CS)); -} - -__attribute__((section(".ramtext"))) -static inline void bb_spi_end(void) { - bb_spi_write((0 << PIN_CLK) | (1 << PIN_CS)); -} - -__attribute__((section(".ramtext"))) -static void spi_single_tx(uint8_t out) { - int bit; - - for (bit = 7; bit >= 0; bit--) { - if (out & (1 << bit)) { - bb_spi_write((0 << PIN_CLK) | (1 << PIN_MOSI)); - bb_spi_write((1 << PIN_CLK) | (1 << PIN_MOSI)); - bb_spi_write((0 << PIN_CLK) | (1 << PIN_MOSI)); - } else { - bb_spi_write((0 << PIN_CLK) | (0 << PIN_MOSI)); - bb_spi_write((1 << PIN_CLK) | (0 << PIN_MOSI)); - bb_spi_write((0 << PIN_CLK) | (0 << PIN_MOSI)); - } - } -} - -__attribute__((section(".ramtext"))) -static uint8_t spi_single_rx(void) { - int bit = 0; - uint8_t in = 0; - - bb_spi_write((1 << PIN_MISO_EN) | (0 << PIN_CLK)); - - while (bit++ < 8) { - bb_spi_write((1 << PIN_MISO_EN) | (1 << PIN_CLK)); - in = (in << 1) | bb_read(); - bb_spi_write((1 << PIN_MISO_EN) | (0 << PIN_CLK)); - } - - return in; -} - -__attribute__((section(".ramtext"))) -static int bb_spi_beginErase4(uint32_t erase_addr) { - // Enable Write-Enable Latch (WEL) - bb_spi_begin(); - spi_single_tx(0x06); - bb_spi_end(); - - bb_spi_begin(); - spi_single_tx(0x20); - spi_single_tx(erase_addr >> 16); - spi_single_tx(erase_addr >> 8); - spi_single_tx(erase_addr >> 0); - bb_spi_end(); - return 0; -} - -__attribute__((section(".ramtext"))) -static int bb_spi_beginWrite(uint32_t addr, const void *v_data, unsigned int count) { - const uint8_t write_cmd = 0x02; - const uint8_t *data = v_data; - unsigned int i; - - #ifdef NDEBUG - if (v_data < (const void *)_flash_cache) { - asm ("ebreak"); - } - if ((v_data + count) > (const void *)&_flash_cache[4096]) { - asm ("ebreak"); - } - #endif - - // Enable Write-Enable Latch (WEL) - bb_spi_begin(); - spi_single_tx(0x06); - bb_spi_end(); - - bb_spi_begin(); - spi_single_tx(write_cmd); - spi_single_tx(addr >> 16); - spi_single_tx(addr >> 8); - spi_single_tx(addr >> 0); - for (i = 0; (i < count) && (i < 256); i++) { - spi_single_tx(*data++); - } - bb_spi_end(); - - return 0; -} - -__attribute__((section(".ramtext"))) -static uint8_t spi_read_status(void) { - uint8_t val; - - bb_spi_begin(); - spi_single_tx(0x05); - val = spi_single_rx(); - bb_spi_end(); - return val; -} - -__attribute__((section(".ramtext"))) -static int bb_spi_is_busy(void) { - return spi_read_status() & (1 << 0); -} - -__attribute__((used)) -uint32_t page_write_log[128]; -__attribute__((used)) -uint32_t page_write_log_offset; - -__attribute__((section(".ramtext"))) -static void bb_spi_write_page(uint32_t flash_address, const uint8_t *data) { - const uint32_t flash_address_end = flash_address + FLASH_PAGE_SIZE; - - // Ensure we're within the target flash address range. - if ((flash_address - FLASH_PARTITION_OFFSET_BYTES) > FLASH_SIZE) { - asm ("ebreak"); - return; - } - if (flash_address < FLASH_PARTITION_OFFSET_BYTES) { - asm ("ebreak"); - return; - } - - if ((flash_address_end - FLASH_PARTITION_OFFSET_BYTES) > FLASH_SIZE) { - asm ("ebreak"); - return; - } - if (flash_address_end < FLASH_PARTITION_OFFSET_BYTES) { - asm ("ebreak"); - return; - } - - // Ensure we're not erasing the middle of a flash bank - if ((flash_address & 0xfff) != 0) { - asm ("ebreak"); - return; - } - - page_write_log[page_write_log_offset++] = flash_address; - if (page_write_log_offset > sizeof(page_write_log) / sizeof(*page_write_log)) { - page_write_log_offset = 0; - } - - while (bb_spi_is_busy()) { - ; // relax - } - bb_spi_beginErase4(flash_address); - while (bb_spi_is_busy()) { - ; // relax - } - while (flash_address < flash_address_end) { - bb_spi_beginWrite(flash_address, data, 256); - while (bb_spi_is_busy()) { - ; // relax - } - flash_address += 256; - data += 256; - } -} - -static inline uint32_t lba2addr(uint32_t block) { - return (0x20000000 + FLASH_PARTITION_OFFSET_BYTES) + (block * FILESYSTEM_BLOCK_SIZE); -} - -void supervisor_flash_init(void) { -} - -uint32_t supervisor_flash_get_block_size(void) { - return FILESYSTEM_BLOCK_SIZE; -} - -uint32_t supervisor_flash_get_block_count(void) { - return FLASH_SIZE / FILESYSTEM_BLOCK_SIZE; -} - -__attribute__((section(".ramtext"))) -void port_internal_flash_flush(void) { - // Skip if data is the same, or if there is no data in the cache - if (_flash_page_addr == NO_CACHE) { - return; - } - if (!_flash_cache_dirty) { - return; - } - - // Disable interrupts and enable bit-bang mode on the SPI flash. - // This function is running from RAM -- otherwise enabling bitbang mode - // would crash the CPU as the program suddenly became an endless stream - // of `0xffffffff`. - bb_spi_irq_setie(0); - bb_spi_write((0 << PIN_CLK) | (1 << PIN_CS)); - bb_spi_en(1); - - bb_spi_write_page(_flash_page_addr & 0x00ffffff, (const uint8_t *)_flash_cache); - - bb_spi_en(0); - bb_spi_irq_setie(1); - - _flash_cache_dirty = false; -} - -mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) { - // Must write out anything in cache before trying to read. - supervisor_flash_flush(); - - uint32_t src = lba2addr(block); - memcpy(dest, (uint8_t *)src, FILESYSTEM_BLOCK_SIZE * num_blocks); - - #if CIRCUITPY_USB_DEVICE - usb_background(); - #endif - - return 0; -} - -mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t lba, uint32_t num_blocks) { - while (num_blocks) { - uint32_t const addr = lba2addr(lba); - uint32_t const page_addr = addr & ~(FLASH_PAGE_SIZE - 1); - - uint32_t count = 8 - (lba % 8); // up to page boundary - count = MIN(num_blocks, count); - - if (page_addr != _flash_page_addr) { - // Write out anything in cache before overwriting it. - supervisor_flash_flush(); - - _flash_page_addr = page_addr; - _flash_cache_dirty = false; - - // Copy the current contents of the entire page into the cache. - memcpy(_flash_cache, (void *)page_addr, FLASH_PAGE_SIZE); - } - - // Overwrite part or all of the page cache with the src data, but only if it's changed. - if (_flash_cache_dirty || memcmp(_flash_cache + (addr & (FLASH_PAGE_SIZE - 1)), src, count * FILESYSTEM_BLOCK_SIZE)) { - memcpy(_flash_cache + (addr & (FLASH_PAGE_SIZE - 1)), src, count * FILESYSTEM_BLOCK_SIZE); - _flash_cache_dirty = true; - } - - // adjust for next run - lba += count; - src += count * FILESYSTEM_BLOCK_SIZE; - num_blocks -= count; - - #if CIRCUITPY_USB_DEVICE - usb_background(); - #endif - } - - return 0; // success -} - -void supervisor_flash_release_cache(void) { -} diff --git a/ports/litex/supervisor/internal_flash.h b/ports/litex/supervisor/internal_flash.h deleted file mode 100644 index 7bc26042ca5..00000000000 --- a/ports/litex/supervisor/internal_flash.h +++ /dev/null @@ -1,15 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2013, 2014 Damien P. George -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries -// -// SPDX-License-Identifier: MIT -#pragma once - -#include -#include - -#include "py/mpconfig.h" - -#define INTERNAL_FLASH_SYSTICK_MASK (0x1ff) // 512ms -#define INTERNAL_FLASH_IDLE_TICK(tick) (((tick) & INTERNAL_FLASH_SYSTICK_MASK) == 2) diff --git a/ports/litex/supervisor/port.c b/ports/litex/supervisor/port.c deleted file mode 100644 index 7f9aab7f7ff..00000000000 --- a/ports/litex/supervisor/port.c +++ /dev/null @@ -1,135 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2017 Scott Shawcroft for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include -#include "supervisor/board.h" -#include "supervisor/port.h" -#include "supervisor/shared/tick.h" -#include "irq.h" -#include "csr.h" - -#include "shared-bindings/microcontroller/__init__.h" - -// Global millisecond tick count. 1024 per second because most RTCs are clocked with 32.768khz -// crystals. -volatile uint64_t raw_ticks = 0; -volatile int subsecond = 0; -void SysTick_Handler(void); -__attribute__((section(".ramtext"))) -void SysTick_Handler(void) { - timer0_ev_pending_write(1); - raw_ticks += 1; - subsecond += 1; - // We track subsecond ticks so that we can increment raw_ticks one extra every 40 ms. We do this - // every 40 except 0 to make it 24 increments and not 25. - if (subsecond == 1000) { - subsecond = 0; - } else if (subsecond % 40 == 0) { - raw_ticks += 1; - } - supervisor_tick(); -} - -static void tick_init(void) { - int t; - - timer0_en_write(0); - t = CONFIG_CLOCK_FREQUENCY / 1000; // 1000 kHz tick - timer0_reload_write(t); - timer0_load_write(t); - timer0_en_write(1); - timer0_ev_enable_write(1); - timer0_ev_pending_write(1); - irq_setmask(irq_getmask() | (1 << TIMER0_INTERRUPT)); -} - -safe_mode_t port_init(void) { - irq_setmask(0); - irq_setie(1); - tick_init(); - return SAFE_MODE_NONE; -} - -extern uint32_t _ebss; -extern uint32_t _heap_start; -extern uint32_t _estack; - -void reset_port(void) { - // i2c_reset(); - // spi_reset(); - // uart_reset(); - // pwmout_reset(); -} - -void reset_to_bootloader(void) { - reboot_ctrl_write(0xac); - for (;;) {} -} - -void reset_cpu(void) { - // "You can reset Fomu by writing a special value to the CSR_REBOOT_CTRL - // register at 0xe0006000L. All writes to this register must start with - // 0xac, to ensure random values aren’t written. We can reboot Fomu by - // simply writing this value" -- - // https://workshop.fomu.im/en/latest/riscv.html - reboot_ctrl_write(0xac); - for (;;) {} -} - -uint32_t *port_heap_get_bottom(void) { - return &_ebss; -} - -uint32_t *port_heap_get_top(void) { - return port_stack_get_limit(); -} - -uint32_t *port_stack_get_limit(void) { - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Warray-bounds" - return port_stack_get_top() - (CIRCUITPY_DEFAULT_STACK_SIZE + CIRCUITPY_EXCEPTION_STACK_SIZE) / sizeof(uint32_t); - #pragma GCC diagnostic pop -} - -uint32_t *port_stack_get_top(void) { - return &_estack; -} - -// Place the word to save just after our BSS section that gets blanked. -void port_set_saved_word(uint32_t value) { - _ebss = value; -} - -uint32_t port_get_saved_word(void) { - return _ebss; -} - -uint64_t port_get_raw_ticks(uint8_t *subticks) { - // Reading 64 bits may take two loads, so turn of interrupts while we do it. - common_hal_mcu_disable_interrupts(); - uint64_t raw_tick_snapshot = raw_ticks; - common_hal_mcu_enable_interrupts(); - if (subticks != NULL) { - *subticks = 0; - } - return raw_tick_snapshot; -} - -// Enable 1/1024 second tick. -void port_enable_tick(void) { -} - -// Disable 1/1024 second tick. -void port_disable_tick(void) { -} - -void port_interrupt_after_ticks(uint32_t ticks) { -} - -// TODO: Add sleep support if the SoC supports sleep. -void port_idle_until_interrupt(void) { -} diff --git a/ports/litex/supervisor/usb.c b/ports/litex/supervisor/usb.c deleted file mode 100644 index f2324a57a6e..00000000000 --- a/ports/litex/supervisor/usb.c +++ /dev/null @@ -1,14 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2018 hathach for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/usb.h" -#include "shared/runtime/interrupt_char.h" -#include "shared/readline/readline.h" - -void init_usb_hardware(void) { - -} diff --git a/ports/mimxrt10xx/common-hal/audiobusio/__init__.c b/ports/mimxrt10xx/common-hal/audiobusio/__init__.c index d51ea773acb..3ed760a1b30 100644 --- a/ports/mimxrt10xx/common-hal/audiobusio/__init__.c +++ b/ports/mimxrt10xx/common-hal/audiobusio/__init__.c @@ -173,7 +173,6 @@ static bool i2s_clocking(I2S_Type *peripheral) { return false; } - static bool i2s_queue_available(i2s_t *self) { return !self->handle.saiQueue[self->handle.queueUser].data; } @@ -280,7 +279,6 @@ static void i2s_transfer_callback(I2S_Type *base, sai_handle_t *handle, status_t } } - void port_i2s_initialize(i2s_t *self, int instance, sai_transceiver_t *config) { if (!i2s_in_use) { // need to set audio pll up! @@ -439,7 +437,3 @@ void port_i2s_pause(i2s_t *self) { void port_i2s_resume(i2s_t *self) { self->paused = false; } - -void i2s_reset(void) { -// this port relies on object finalizers for reset -} diff --git a/ports/mimxrt10xx/common-hal/audiobusio/__init__.h b/ports/mimxrt10xx/common-hal/audiobusio/__init__.h index 00d016b77b4..174b93ecf1c 100644 --- a/ports/mimxrt10xx/common-hal/audiobusio/__init__.h +++ b/ports/mimxrt10xx/common-hal/audiobusio/__init__.h @@ -27,7 +27,6 @@ typedef struct { } i2s_t; -void i2s_reset(void); void port_i2s_initialize(i2s_t *self, int instance, sai_transceiver_t *config); void port_i2s_deinit(i2s_t *self); bool port_i2s_deinited(i2s_t *self); diff --git a/ports/mimxrt10xx/common-hal/busio/I2C.c b/ports/mimxrt10xx/common-hal/busio/I2C.c index 93c0ab30123..86ec33f0cc5 100644 --- a/ports/mimxrt10xx/common-hal/busio/I2C.c +++ b/ports/mimxrt10xx/common-hal/busio/I2C.c @@ -145,11 +145,6 @@ void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, claim_pin(self->scl->pin); } -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - common_hal_never_reset_pin(self->sda->pin); - common_hal_never_reset_pin(self->scl->pin); -} - bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { return self->sda == NULL; } diff --git a/ports/mimxrt10xx/common-hal/busio/SPI.c b/ports/mimxrt10xx/common-hal/busio/SPI.c index 90695be4b4e..0fd54e3e779 100644 --- a/ports/mimxrt10xx/common-hal/busio/SPI.c +++ b/ports/mimxrt10xx/common-hal/busio/SPI.c @@ -185,16 +185,6 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, } } -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { - common_hal_never_reset_pin(self->clock->pin); - if (self->mosi != NULL) { - common_hal_never_reset_pin(self->mosi->pin); - } - if (self->miso != NULL) { - common_hal_never_reset_pin(self->miso->pin); - } -} - bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { return self->clock == NULL; } diff --git a/ports/mimxrt10xx/common-hal/busio/UART.c b/ports/mimxrt10xx/common-hal/busio/UART.c index d372865e8a1..cd25a400bf1 100644 --- a/ports/mimxrt10xx/common-hal/busio/UART.c +++ b/ports/mimxrt10xx/common-hal/busio/UART.c @@ -33,7 +33,6 @@ // arrays use 0 based numbering: UART1 is stored at index 0 static bool reserved_uart[MP_ARRAY_SIZE(mcu_uart_banks)]; -static bool never_reset_uart[MP_ARRAY_SIZE(mcu_uart_banks)]; #if IMXRT11XX #define UART_CLOCK_FREQ (24000000) @@ -63,15 +62,6 @@ static void config_periph_pin(const mcu_periph_obj_t *periph) { | IOMUXC_SW_PAD_CTL_PAD_SRE(0)); } -void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { - never_reset_uart[self->index] = true; - common_hal_never_reset_pin(self->tx); - common_hal_never_reset_pin(self->rx); - common_hal_never_reset_pin(self->rts); - common_hal_never_reset_pin(self->cts); - common_hal_never_reset_pin(self->rs485_dir); -} - void common_hal_busio_uart_construct(busio_uart_obj_t *self, const mcu_pin_obj_t *tx, const mcu_pin_obj_t *rx, const mcu_pin_obj_t *rts, const mcu_pin_obj_t *cts, @@ -348,7 +338,6 @@ void common_hal_busio_uart_deinit(busio_uart_obj_t *self) { gc_free(self->ringbuf); reserved_uart[self->index] = false; - never_reset_uart[self->index] = false; common_hal_reset_pin(self->rx); common_hal_reset_pin(self->tx); diff --git a/ports/mimxrt10xx/common-hal/canio/CAN.c b/ports/mimxrt10xx/common-hal/canio/CAN.c index 0b598af1521..41ff74f7a54 100644 --- a/ports/mimxrt10xx/common-hal/canio/CAN.c +++ b/ports/mimxrt10xx/common-hal/canio/CAN.c @@ -428,18 +428,21 @@ void common_hal_canio_can_deinit(canio_can_obj_t *self) { self->data = NULL; } + // Find which hardware instance we are so we can free its slot. + size_t instance = MP_ARRAY_SIZE(can_objs); + for (size_t i = 0; i < MP_ARRAY_SIZE(can_objs); i++) { + if (can_objs[i] == self) { + instance = i; + break; + } + } + if (instance < MP_ARRAY_SIZE(can_objs)) { + can_objs[instance] = NULL; + } + common_hal_reset_pin(self->rx_pin); common_hal_reset_pin(self->tx_pin); self->rx_pin = NULL; self->tx_pin = NULL; } - -void common_hal_canio_reset(void) { - for (size_t i = 0; i < MP_ARRAY_SIZE(can_objs); i++) { - if (can_objs[i]) { - common_hal_canio_can_deinit(can_objs[i]); - can_objs[i] = NULL; - } - } -} diff --git a/ports/mimxrt10xx/common-hal/digitalio/DigitalInOut.c b/ports/mimxrt10xx/common-hal/digitalio/DigitalInOut.c index 48671f3fea9..1e121707c78 100644 --- a/ports/mimxrt10xx/common-hal/digitalio/DigitalInOut.c +++ b/ports/mimxrt10xx/common-hal/digitalio/DigitalInOut.c @@ -52,11 +52,6 @@ digitalinout_result_t common_hal_digitalio_digitalinout_construct( return DIGITALINOUT_OK; } -void common_hal_digitalio_digitalinout_never_reset( - digitalio_digitalinout_obj_t *self) { - common_hal_never_reset_pin(self->pin); -} - bool common_hal_digitalio_digitalinout_deinited(digitalio_digitalinout_obj_t *self) { return self->pin == NULL; } diff --git a/ports/mimxrt10xx/common-hal/microcontroller/Pin.c b/ports/mimxrt10xx/common-hal/microcontroller/Pin.c index c20a1738af0..700e384315e 100644 --- a/ports/mimxrt10xx/common-hal/microcontroller/Pin.c +++ b/ports/mimxrt10xx/common-hal/microcontroller/Pin.c @@ -14,7 +14,6 @@ #include "py/gc.h" static bool claimed_pins[PAD_COUNT]; -static bool never_reset_pins[PAD_COUNT]; // Default is that no pins are forbidden to reset. MP_WEAK const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[] = { @@ -36,19 +35,6 @@ static bool _reset_forbidden(const mcu_pin_obj_t *pin) { // IOMUXC index, used for iterating through pins and accessing reset information, // and GPIO port and number, used to store claimed and reset tagging. The two number // systems are not related and one cannot determine the other without a pin object -void reset_all_pins(void) { - for (uint8_t i = 0; i < PAD_COUNT; i++) { - claimed_pins[i] = never_reset_pins[i]; - } - for (uint8_t i = 0; i < PAD_COUNT; i++) { - mcu_pin_obj_t *pin = mcu_pin_globals.map.table[i].value; - if (never_reset_pins[pin->mux_idx]) { - continue; - } - common_hal_reset_pin(pin); - } -} - MP_WEAK bool mimxrt10xx_board_reset_pin_number(const mcu_pin_obj_t *pin) { return false; } @@ -70,7 +56,6 @@ void common_hal_reset_pin(const mcu_pin_obj_t *pin) { } disable_pin_change_interrupt(pin); - never_reset_pins[pin->mux_idx] = false; claimed_pins[pin->mux_idx] = false; // Make sure this pin's GPIO is set to input. Otherwise, output values could interfere @@ -86,13 +71,6 @@ void common_hal_reset_pin(const mcu_pin_obj_t *pin) { *(uint32_t *)pin->cfg_reg = pin->pad_reset; } -void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - if (pin == NULL) { - return; - } - never_reset_pins[pin->mux_idx] = true; -} - bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { return !claimed_pins[pin->mux_idx]; } diff --git a/ports/mimxrt10xx/common-hal/microcontroller/Pin.h b/ports/mimxrt10xx/common-hal/microcontroller/Pin.h index c40a4dc2a68..47f9e2b215a 100644 --- a/ports/mimxrt10xx/common-hal/microcontroller/Pin.h +++ b/ports/mimxrt10xx/common-hal/microcontroller/Pin.h @@ -10,9 +10,12 @@ #include "periph.h" #include "pins.h" -void reset_all_pins(void); +void common_hal_reset_pin(const mcu_pin_obj_t *pin); void claim_pin(const mcu_pin_obj_t *pin); +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); +void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin); + // List of pins that should never be reset. extern const mcu_pin_obj_t *mimxrt10xx_reset_forbidden_pins[]; diff --git a/ports/mimxrt10xx/common-hal/pwmio/PWMOut.c b/ports/mimxrt10xx/common-hal/pwmio/PWMOut.c index 169ce096307..8fe4d16e011 100644 --- a/ports/mimxrt10xx/common-hal/pwmio/PWMOut.c +++ b/ports/mimxrt10xx/common-hal/pwmio/PWMOut.c @@ -65,10 +65,6 @@ static uint16_t _outen_mask(pwm_submodule_t submodule, pwm_channels_t channel) { return outen_mask; } -void common_hal_pwmio_pwmout_never_reset(pwmio_pwmout_obj_t *self) { - common_hal_never_reset_pin(self->pin); -} - static void _maybe_disable_clock(uint8_t instance) { if ((_flexpwms[instance]->MCTRL & PWM_MCTRL_RUN_MASK) == 0) { CLOCK_DisableClock(_flexpwm_clocks[instance][0]); diff --git a/ports/mimxrt10xx/common-hal/rtc/RTC.h b/ports/mimxrt10xx/common-hal/rtc/RTC.h index 851b08970bb..50b0539d4d6 100644 --- a/ports/mimxrt10xx/common-hal/rtc/RTC.h +++ b/ports/mimxrt10xx/common-hal/rtc/RTC.h @@ -8,4 +8,3 @@ #pragma once extern void rtc_init(void); -extern void rtc_reset(void); diff --git a/ports/mimxrt10xx/linking/common.ld b/ports/mimxrt10xx/linking/common.ld index 2c6be0dd129..1131f2b6f1f 100644 --- a/ports/mimxrt10xx/linking/common.ld +++ b/ports/mimxrt10xx/linking/common.ld @@ -69,6 +69,12 @@ SECTIONS .text : { . = ALIGN(4); + __property_getter_start = .; + *(.property_getter) + __property_getter_end = .; + __property_getset_start = .; + *(.property_getset) + __property_getset_end = .; *(EXCLUDE_FILE( *fsl_flexspi.o *cd_ci_hs.o @@ -210,4 +216,9 @@ SECTIONS __StackTop = ORIGIN(DTCM) + LENGTH(DTCM); .ARM.attributes 0 : { *(.ARM.attributes) } + + ASSERT((__property_getter_end - __property_getter_start) % 8 == 0, + "the .property_getter section must hold whole 8-byte objects") + ASSERT((__property_getset_end - __property_getset_start) % 12 == 0, + "the .property_getset section must hold whole 12-byte objects") } diff --git a/ports/mimxrt10xx/mpconfigport.mk b/ports/mimxrt10xx/mpconfigport.mk index b3f078c7ecf..f6286402fca 100644 --- a/ports/mimxrt10xx/mpconfigport.mk +++ b/ports/mimxrt10xx/mpconfigport.mk @@ -1,3 +1,6 @@ +# Properties get a dedicated linker section here, so they can drop the unused slots. +CIRCUITPY_OPTIMIZE_PROPERTY_FLASH_SIZE ?= 1 + LD_FILE = $(FLASH).ld $(CHIP_FAMILY).ld imxrt10xx.ld INTERNAL_LIBM = 1 @@ -30,3 +33,6 @@ CIRCUITPY_USB_MIDI = 1 LONGINT_IMPL = MPZ CIRCUITPY_BUILD_EXTENSIONS ?= hex,uf2 + +# Cleanup is performed by GC finalizers (__del__ -> deinit) instead of a bulk pin reset. +CIRCUITPY_BULK_RESET = 0 diff --git a/ports/mimxrt10xx/supervisor/port.c b/ports/mimxrt10xx/supervisor/port.c index 62d2569cfde..9b0b4520bae 100644 --- a/ports/mimxrt10xx/supervisor/port.c +++ b/ports/mimxrt10xx/supervisor/port.c @@ -22,6 +22,7 @@ #include "common-hal/microcontroller/Pin.h" #include "common-hal/rtc/RTC.h" +#include "shared-bindings/rtc/__init__.h" #include "common-hal/busio/SPI.h" #include "shared-bindings/microcontroller/__init__.h" @@ -415,7 +416,7 @@ safe_mode_t port_init(void) { #endif // Note that `reset_port` CANNOT GO HERE, unlike other ports, because `board_init` hasn't been - // run yet, which uses `never_reset` to protect critical pins from being reset by `reset_port`. + // run yet, which may claim critical pins that should not be reset by `reset_port`. if (board_requests_safe_mode()) { return SAFE_MODE_USER; @@ -425,26 +426,10 @@ safe_mode_t port_init(void) { } void reset_port(void) { - #if CIRCUITPY_AUDIOIO - audio_dma_reset(); - #endif - - #if CIRCUITPY_AUDIOBUSIO - i2s_reset(); - #endif - - #if CIRCUITPY_TOUCHIO && CIRCUITPY_TOUCHIO_USE_NATIVE - touchin_reset(); - #endif - #if CIRCUITPY_RTC rtc_reset(); #endif - #if CIRCUITPY_PEW - pew_reset(); - #endif - // reset_event_system(); } diff --git a/ports/nordic/boards/bless_dev_board_multi_sensor/mpconfigboard.mk b/ports/nordic/boards/bless_dev_board_multi_sensor/mpconfigboard.mk index 90b0908505e..18dfd6e3e8f 100644 --- a/ports/nordic/boards/bless_dev_board_multi_sensor/mpconfigboard.mk +++ b/ports/nordic/boards/bless_dev_board_multi_sensor/mpconfigboard.mk @@ -6,3 +6,5 @@ USB_MANUFACTURER = "Switch Science, Inc." MCU_CHIP = nrf52840 INTERNAL_FLASH_FILESYSTEM = 1 + +CIRCUITPY_MSGPACK = 0 diff --git a/ports/nordic/boards/bluemicro833/board.c b/ports/nordic/boards/bluemicro833/board.c index 1abc6f4e79c..13d66ace069 100644 --- a/ports/nordic/boards/bluemicro833/board.c +++ b/ports/nordic/boards/bluemicro833/board.c @@ -13,8 +13,6 @@ #include "nrf_gpio.h" void board_init(void) { - // "never_reset" the pin here because CircuitPython will try to reset pins after a VM run otherwise. - never_reset_pin_number(POWER_SWITCH_PIN->number); // Turn on power to sensors and neopixels. nrf_gpio_cfg(POWER_SWITCH_PIN->number, NRF_GPIO_PIN_DIR_OUTPUT, diff --git a/ports/nordic/boards/bluemicro840/mpconfigboard.mk b/ports/nordic/boards/bluemicro840/mpconfigboard.mk index 16d36704a3e..70b8e701a4c 100644 --- a/ports/nordic/boards/bluemicro840/mpconfigboard.mk +++ b/ports/nordic/boards/bluemicro840/mpconfigboard.mk @@ -6,3 +6,5 @@ USB_MANUFACTURER = "nrf52.jpconstantineau.com" MCU_CHIP = nrf52840 INTERNAL_FLASH_FILESYSTEM = 1 + +CIRCUITPY_MSGPACK = 0 diff --git a/ports/nordic/boards/clue_nrf52840_express/board.c b/ports/nordic/boards/clue_nrf52840_express/board.c index 0b331b63a96..2d947dc1456 100644 --- a/ports/nordic/boards/clue_nrf52840_express/board.c +++ b/ports/nordic/boards/clue_nrf52840_express/board.c @@ -29,7 +29,6 @@ void board_init(void) { fourwire_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus; busio_spi_obj_t *spi = &bus->inline_bus; common_hal_busio_spi_construct(spi, &pin_P0_14, &pin_P0_15, NULL, false); - common_hal_busio_spi_never_reset(spi); bus->base.type = &fourwire_fourwire_type; common_hal_fourwire_fourwire_construct(bus, diff --git a/ports/nordic/boards/common.template.ld b/ports/nordic/boards/common.template.ld index dda31e4fdce..3828e3e7f9c 100644 --- a/ports/nordic/boards/common.template.ld +++ b/ports/nordic/boards/common.template.ld @@ -62,6 +62,12 @@ SECTIONS .text : { . = ALIGN(4); + __property_getter_start = .; + *(.property_getter) + __property_getter_end = .; + __property_getset_start = .; + *(.property_getset) + __property_getset_end = .; *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.text))) /* .text sections (code) */ *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.text*))) /* .text* sections (code) */ *(.rodata) /* .rodata sections (constants, strings, etc.) */ @@ -155,4 +161,9 @@ SECTIONS */ .ARM.attributes 0 : { *(.ARM.attributes) } + + ASSERT((__property_getter_end - __property_getter_start) % 8 == 0, + "the .property_getter section must hold whole 8-byte objects") + ASSERT((__property_getset_end - __property_getset_start) % 12 == 0, + "the .property_getset section must hold whole 12-byte objects") } diff --git a/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk b/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk index fc59a101e45..7e928181948 100644 --- a/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk +++ b/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk @@ -12,3 +12,4 @@ CIRCUITPY_AUDIOIO = 0 CIRCUITPY_DISPLAYIO = 1 CIRCUITPY_STAGE = 1 CIRCUITPY_DIGITALINOUT_PROTOCOL = 0 +CIRCUITPY_MSGPACK = 0 diff --git a/ports/nordic/boards/espruino_banglejs2/board.c b/ports/nordic/boards/espruino_banglejs2/board.c index 09adb43f8be..8761fa813b1 100644 --- a/ports/nordic/boards/espruino_banglejs2/board.c +++ b/ports/nordic/boards/espruino_banglejs2/board.c @@ -21,18 +21,15 @@ uint32_t last_down_ticks_ms; void board_init(void) { common_hal_digitalio_digitalinout_construct(&extcomin, &pin_P0_06); common_hal_digitalio_digitalinout_switch_to_output(&extcomin, true, DRIVE_MODE_PUSH_PULL); - common_hal_digitalio_digitalinout_never_reset(&extcomin); common_hal_digitalio_digitalinout_construct(&display_on, &pin_P0_07); common_hal_digitalio_digitalinout_switch_to_output(&display_on, true, DRIVE_MODE_PUSH_PULL); - common_hal_digitalio_digitalinout_never_reset(&display_on); sharpdisplay_framebuffer_obj_t *fb = &allocate_display_bus()->sharpdisplay; fb->base.type = &sharpdisplay_framebuffer_type; busio_spi_obj_t *spi = &fb->inline_bus; common_hal_busio_spi_construct(spi, &pin_P0_26, &pin_P0_27, NULL, false); - common_hal_busio_spi_never_reset(spi); common_hal_sharpdisplay_framebuffer_construct(fb, spi, &pin_P0_05, 500000, 176, 176, true); diff --git a/ports/nordic/boards/hiibot_bluefi/board.c b/ports/nordic/boards/hiibot_bluefi/board.c index a05be7a0a63..fe03e94c8ba 100644 --- a/ports/nordic/boards/hiibot_bluefi/board.c +++ b/ports/nordic/boards/hiibot_bluefi/board.c @@ -29,7 +29,6 @@ void board_init(void) { fourwire_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus; busio_spi_obj_t *spi = &bus->inline_bus; common_hal_busio_spi_construct(spi, &pin_P0_07, &pin_P1_08, NULL, false); // SCK, MOSI, MISO, not half-duplex - common_hal_busio_spi_never_reset(spi); bus->base.type = &fourwire_fourwire_type; common_hal_fourwire_fourwire_construct(bus, diff --git a/ports/nordic/boards/makerdiary_m60_keyboard/board.c b/ports/nordic/boards/makerdiary_m60_keyboard/board.c index 65595a6bca5..71b2995b472 100644 --- a/ports/nordic/boards/makerdiary_m60_keyboard/board.c +++ b/ports/nordic/boards/makerdiary_m60_keyboard/board.c @@ -19,25 +19,6 @@ static void power_on(void) { nrf_gpio_pin_write(POWER_SWITCH_PIN->number, true); } -static void preserve_and_release_battery_pin(void) { - // Preserve the battery state. The battery is enabled by default in factory bootloader. - // Reset claimed_pins so user can control pin's state in the vm. - // The code below doesn't actually reset the pin's state, but only set the flags. - reset_pin_number(POWER_SWITCH_PIN->number); // clear claimed_pins and never_reset_pins - never_reset_pin_number(POWER_SWITCH_PIN->number); // set never_reset_pins -} - void board_init(void) { - // As of cpy 8.1.0, board_init() runs after reset_ports() on first run. That means - // never_reset_pins won't be set at boot, the battery pin is reset, causing system - // shutdown. - // So if we need to run on battery, we must enable the battery here. power_on(); - preserve_and_release_battery_pin(); } - -void reset_board(void) { - preserve_and_release_battery_pin(); -} - -// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/nordic/boards/makerdiary_nrf52840_m2_devkit/board.c b/ports/nordic/boards/makerdiary_nrf52840_m2_devkit/board.c index 4f9c051478a..5c2427b8142 100644 --- a/ports/nordic/boards/makerdiary_nrf52840_m2_devkit/board.c +++ b/ports/nordic/boards/makerdiary_nrf52840_m2_devkit/board.c @@ -30,7 +30,6 @@ void board_init(void) { fourwire_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus; busio_spi_obj_t *spi = &bus->inline_bus; common_hal_busio_spi_construct(spi, &pin_P0_11, &pin_P0_12, NULL, false); - common_hal_busio_spi_never_reset(spi); bus->base.type = &fourwire_fourwire_type; common_hal_fourwire_fourwire_construct(bus, diff --git a/ports/nordic/boards/nice_nano/mpconfigboard.mk b/ports/nordic/boards/nice_nano/mpconfigboard.mk index 511a754e69a..6489102626a 100644 --- a/ports/nordic/boards/nice_nano/mpconfigboard.mk +++ b/ports/nordic/boards/nice_nano/mpconfigboard.mk @@ -6,3 +6,5 @@ USB_MANUFACTURER = "Nice Keyboards" MCU_CHIP = nrf52840 INTERNAL_FLASH_FILESYSTEM = 1 + +CIRCUITPY_MSGPACK = 0 diff --git a/ports/nordic/boards/ohs2020_badge/board.c b/ports/nordic/boards/ohs2020_badge/board.c index d16d99473f0..1f64494af5f 100644 --- a/ports/nordic/boards/ohs2020_badge/board.c +++ b/ports/nordic/boards/ohs2020_badge/board.c @@ -29,7 +29,6 @@ void board_init(void) { fourwire_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus; busio_spi_obj_t *spi = &bus->inline_bus; common_hal_busio_spi_construct(spi, &pin_P0_11, &pin_P0_12, NULL, false); - common_hal_busio_spi_never_reset(spi); bus->base.type = &fourwire_fourwire_type; common_hal_fourwire_fourwire_construct(bus, diff --git a/ports/nordic/boards/pillbug/mpconfigboard.mk b/ports/nordic/boards/pillbug/mpconfigboard.mk index d917ba01578..d531c838e18 100644 --- a/ports/nordic/boards/pillbug/mpconfigboard.mk +++ b/ports/nordic/boards/pillbug/mpconfigboard.mk @@ -6,3 +6,5 @@ USB_MANUFACTURER = "Mechwild" MCU_CHIP = nrf52840 INTERNAL_FLASH_FILESYSTEM = 1 + +CIRCUITPY_MSGPACK = 0 diff --git a/ports/nordic/boards/supermini_nrf52840/mpconfigboard.mk b/ports/nordic/boards/supermini_nrf52840/mpconfigboard.mk index 699d19b7706..b4d8d78ed9c 100644 --- a/ports/nordic/boards/supermini_nrf52840/mpconfigboard.mk +++ b/ports/nordic/boards/supermini_nrf52840/mpconfigboard.mk @@ -6,3 +6,5 @@ USB_MANUFACTURER = "ICBbuy" MCU_CHIP = nrf52840 INTERNAL_FLASH_FILESYSTEM = 1 + +CIRCUITPY_MSGPACK = 0 diff --git a/ports/nordic/boards/teenage_engineering_sp1/board.c b/ports/nordic/boards/teenage_engineering_sp1/board.c index ad729a5e5fc..3cfa5110202 100644 --- a/ports/nordic/boards/teenage_engineering_sp1/board.c +++ b/ports/nordic/boards/teenage_engineering_sp1/board.c @@ -110,6 +110,8 @@ static void stop_pwm(NRF_PWM_Type *pwm) { pwm->ENABLE = 0; } +static void apply_all_pin_defaults(void); + void board_early_init(void) { // Feed the bootloader's watchdog before anything else. This is the first // CircuitPython code to run on the board: port_init() calls it before it @@ -193,13 +195,18 @@ void board_early_init(void) { nrfx_rtc_init(&wake_rtc, &wake_rtc_config, wake_rtc_handler); arm_wake_rtc(); nrfx_rtc_enable(&wake_rtc); + + // Apply the board's resting state here, so nothing floats while we + // wait for code.py. The heartbeat LED is excluded by the guard in + // board_reset_pin_number() because the blink is still lit. + apply_all_pin_defaults(); } -// Pins that must not float. reset_all_pins() and reset_pin_number() ask the -// board for each pin, so this configuration is re-applied after every reset -// rather than the pin being left in its default (disconnected) state. +// Pins that must not float. reset_pin_number() asks the board for each pin, +// so this configuration is re-applied after every reset rather than the pin +// being left in its default (disconnected) state. // -// None of these are marked never-reset, so Python can still claim them. This +// None of these are claimed, so Python can still claim them. This // only makes the resting state between runs a defined, safe one. static const uint8_t default_low_pins[] = { // 3.072 MHz oscillator enable. Held low: it draws current straight through @@ -268,22 +275,23 @@ static bool apply_pin_default(uint8_t pin_number) { } // Put every pin this board has an opinion about into its resting state at once. +// Goes through board_reset_pin_number(), not apply_pin_default() directly, so +// the boot heartbeat LED is left alone while it is lit. static void apply_all_pin_defaults(void) { - apply_pin_default(PIN_FUNCTION_BUTTON); - apply_pin_default(PIN_EMMC_RESET); - apply_pin_default(PIN_EMMC_VCCQ_EN); + board_reset_pin_number(PIN_FUNCTION_BUTTON); + board_reset_pin_number(PIN_EMMC_RESET); + board_reset_pin_number(PIN_EMMC_VCCQ_EN); for (size_t i = 0; i < MP_ARRAY_SIZE(led_pins); i++) { - apply_pin_default(led_pins[i]); + board_reset_pin_number(led_pins[i]); } for (size_t i = 0; i < MP_ARRAY_SIZE(default_low_pins); i++) { - apply_pin_default(default_low_pins[i]); + board_reset_pin_number(default_low_pins[i]); } } bool board_reset_pin_number(uint8_t pin_number) { - // main() calls reset_all_pins() immediately after port_init(), so without - // this the boot up heartbeat blink would last microseconds and show - // nothing. board_init() hands the pin back. + // A reset of this pin while the heartbeat is lit must not disturb it; + // board_init() hands the pin back. if (heartbeat_lit && pin_number == PIN_LED_HEARTBEAT) { return true; } diff --git a/ports/nordic/common-hal/_bleio/Connection.c b/ports/nordic/common-hal/_bleio/Connection.c index fbd3263b725..e34aa56c4e2 100644 --- a/ports/nordic/common-hal/_bleio/Connection.c +++ b/ports/nordic/common-hal/_bleio/Connection.c @@ -332,6 +332,21 @@ bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) { return self->connection->pair_status == PAIR_PAIRED; } +// Authenticated (MITM-protected) pairing - numeric comparison - is implemented on the +// espressif port only. This port can't tell a "Just Works" bond from an authenticated +// one, so report the conservative answer rather than a guarantee it can't make. +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + return false; +} + +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { + return mp_const_none; +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + mp_raise_NotImplementedError(NULL); +} + bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { if (self->connection == NULL) { return false; diff --git a/ports/nordic/common-hal/alarm/pin/PinAlarm.c b/ports/nordic/common-hal/alarm/pin/PinAlarm.c index 3359faa74e0..cdafadebae2 100644 --- a/ports/nordic/common-hal/alarm/pin/PinAlarm.c +++ b/ports/nordic/common-hal/alarm/pin/PinAlarm.c @@ -171,8 +171,6 @@ static void configure_pins_for_sleep(void) { void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) { // Bitmask of wake up settings. - size_t high_count = 0; - size_t low_count = 0; int pin_number = -1; for (size_t i = 0; i < n_alarms; i++) { @@ -185,10 +183,8 @@ void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_ob // mp_printf(&mp_plat_print, "alarm_pin_pinalarm_set_alarms(pin#=%d, val=%d, pull=%d)\r\n", pin_number, alarm->value, alarm->pull); if (alarm->value) { high_alarms |= 1ull << pin_number; - high_count++; } else { low_alarms |= 1ull << pin_number; - low_count++; } if (alarm->pull) { pull_pins |= 1ull << pin_number; diff --git a/ports/nordic/common-hal/audiobusio/I2SOut.c b/ports/nordic/common-hal/audiobusio/I2SOut.c index 46b9279cbcc..b7328e5f7e2 100644 --- a/ports/nordic/common-hal/audiobusio/I2SOut.c +++ b/ports/nordic/common-hal/audiobusio/I2SOut.c @@ -240,7 +240,15 @@ void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t *self) { return; } NRF_I2S->TASKS_STOP = 1; + // Mirror i2s_reset(): clear the interrupt enable and disconnect all + // PSEL lines so a later construct starts from a known state. + NRF_I2S->INTENCLR = I2S_INTENSET_TXPTRUPD_Msk; NRF_I2S->ENABLE = I2S_ENABLE_ENABLE_Disabled; + NRF_I2S->PSEL.MCK = 0xFFFFFFFF; + NRF_I2S->PSEL.SCK = 0xFFFFFFFF; + NRF_I2S->PSEL.LRCK = 0xFFFFFFFF; + NRF_I2S->PSEL.SDOUT = 0xFFFFFFFF; + NRF_I2S->PSEL.SDIN = 0xFFFFFFFF; if (self->external_clock) { nrf_gpio_cfg_default(self->bit_clock_pin_number); nrf_gpio_cfg_default(self->word_select_pin_number); @@ -360,18 +368,3 @@ void i2s_background(void) { } } } - -void i2s_reset(void) { - NRF_I2S->TASKS_STOP = 1; - NRF_I2S->INTENCLR = I2S_INTENSET_TXPTRUPD_Msk; - NRF_I2S->ENABLE = I2S_ENABLE_ENABLE_Disabled; - NRF_I2S->PSEL.MCK = 0xFFFFFFFF; - NRF_I2S->PSEL.SCK = 0xFFFFFFFF; - NRF_I2S->PSEL.LRCK = 0xFFFFFFFF; - NRF_I2S->PSEL.SDOUT = 0xFFFFFFFF; - NRF_I2S->PSEL.SDIN = 0xFFFFFFFF; - if (instance) { - supervisor_disable_tick(); - } - instance = NULL; -} diff --git a/ports/nordic/common-hal/audiobusio/I2SOut.h b/ports/nordic/common-hal/audiobusio/I2SOut.h index 27e077c0649..c2bb8db62aa 100644 --- a/ports/nordic/common-hal/audiobusio/I2SOut.h +++ b/ports/nordic/common-hal/audiobusio/I2SOut.h @@ -38,5 +38,4 @@ typedef struct { bool single_buffer : 1; } audiobusio_i2sout_obj_t; -void i2s_reset(void); void i2s_background(void); diff --git a/ports/nordic/common-hal/busio/I2C.c b/ports/nordic/common-hal/busio/I2C.c index 999737f632b..cdcfc293abe 100644 --- a/ports/nordic/common-hal/busio/I2C.c +++ b/ports/nordic/common-hal/busio/I2C.c @@ -41,12 +41,6 @@ static twim_peripheral_t twim_peripherals[] = { #endif }; - -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - never_reset_pin_number(self->scl_pin_number); - never_reset_pin_number(self->sda_pin_number); -} - static mp_negative_errno_t twi_error_to_mp(const nrfx_err_t err) { switch (err) { case NRFX_ERROR_DRV_TWI_ERR_ANACK: diff --git a/ports/nordic/common-hal/busio/SPI.c b/ports/nordic/common-hal/busio/SPI.c index 5e0f2b242dc..24d04e685e4 100644 --- a/ports/nordic/common-hal/busio/SPI.c +++ b/ports/nordic/common-hal/busio/SPI.c @@ -67,17 +67,6 @@ static const spim_peripheral_t spim_peripherals[] = { // https://infocenter.nordicsemi.com/index.jsp?topic=%2Ferrata_nRF52840_Rev2%2FERR%2FnRF52840%2FRev2%2Flatest%2Fanomaly_840_198.html static uint8_t *spim3_transmit_buffer = (uint8_t *)SPIM3_BUFFER_RAM_START_ADDR; -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { - for (size_t i = 0; i < MP_ARRAY_SIZE(spim_peripherals); i++) { - if (self->spim_peripheral == &spim_peripherals[i]) { - never_reset_pin_number(self->clock_pin_number); - never_reset_pin_number(self->MOSI_pin_number); - never_reset_pin_number(self->MISO_pin_number); - break; - } - } -} - // Convert frequency to clock-speed-dependent value. Choose the next lower baudrate if in between // available baudrates. static nrf_spim_frequency_t baudrate_to_spim_frequency(const uint32_t baudrate) { diff --git a/ports/nordic/common-hal/busio/UART.c b/ports/nordic/common-hal/busio/UART.c index 7a4219a9728..016ecc2cc64 100644 --- a/ports/nordic/common-hal/busio/UART.c +++ b/ports/nordic/common-hal/busio/UART.c @@ -36,8 +36,6 @@ static nrfx_uarte_t nrfx_uartes[] = { #endif }; -static bool never_reset[NRFX_UARTE0_ENABLED + NRFX_UARTE1_ENABLED]; - static uint32_t get_nrf_baud(uint32_t baudrate) { static const struct { @@ -111,30 +109,6 @@ static void uart_callback_irq(const nrfx_uarte_event_t *event, void *context) { } } -void uart_reset(void) { - for (size_t i = 0; i < MP_ARRAY_SIZE(nrfx_uartes); i++) { - if (never_reset[i]) { - continue; - } - nrfx_uarte_uninit(&nrfx_uartes[i]); - } -} - -void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { - // Don't never reset objects on the heap. - if (gc_alloc_possible() && gc_ptr_on_heap(self)) { - return; - } - for (size_t i = 0; i < MP_ARRAY_SIZE(nrfx_uartes); i++) { - if (self->uarte == &nrfx_uartes[i]) { - never_reset[i] = true; - break; - } - } - never_reset_pin_number(self->tx_pin_number); - never_reset_pin_number(self->rx_pin_number); -} - void common_hal_busio_uart_construct(busio_uart_obj_t *self, const mcu_pin_obj_t *tx, const mcu_pin_obj_t *rx, const mcu_pin_obj_t *rts, const mcu_pin_obj_t *cts, @@ -251,13 +225,6 @@ void common_hal_busio_uart_deinit(busio_uart_obj_t *self) { self->rts_pin_number = NO_PIN; self->cts_pin_number = NO_PIN; ringbuf_deinit(&self->ringbuf); - - for (size_t i = 0; i < MP_ARRAY_SIZE(nrfx_uartes); i++) { - if (self->uarte == &nrfx_uartes[i]) { - never_reset[i] = false; - break; - } - } } } diff --git a/ports/nordic/common-hal/busio/UART.h b/ports/nordic/common-hal/busio/UART.h index 10e881ced5a..6af34a565cd 100644 --- a/ports/nordic/common-hal/busio/UART.h +++ b/ports/nordic/common-hal/busio/UART.h @@ -29,5 +29,3 @@ typedef struct { uint8_t cts_pin_number; uint8_t rts_pin_number; } busio_uart_obj_t; - -void uart_reset(void); diff --git a/ports/nordic/common-hal/digitalio/DigitalInOut.c b/ports/nordic/common-hal/digitalio/DigitalInOut.c index 58205c9b992..a302c94bcc5 100644 --- a/ports/nordic/common-hal/digitalio/DigitalInOut.c +++ b/ports/nordic/common-hal/digitalio/DigitalInOut.c @@ -9,11 +9,6 @@ #include "nrf_gpio.h" -void common_hal_digitalio_digitalinout_never_reset( - digitalio_digitalinout_obj_t *self) { - never_reset_pin_number(self->pin->number); -} - digitalinout_result_t common_hal_digitalio_digitalinout_construct( digitalio_digitalinout_obj_t *self, const mcu_pin_obj_t *pin) { claim_pin(pin); diff --git a/ports/nordic/common-hal/emmcio/EMMC.c b/ports/nordic/common-hal/emmcio/EMMC.c index 703d9bcb6fc..043655a7b7f 100644 --- a/ports/nordic/common-hal/emmcio/EMMC.c +++ b/ports/nordic/common-hal/emmcio/EMMC.c @@ -833,8 +833,7 @@ void emmcio_emmc_release_hardware(void) { } static void emmc_claim_pins(const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, - const mcu_pin_obj_t *data, const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, - bool never_reset) { + const mcu_pin_obj_t *data, const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq) { emmc_pinout.clk = clock->number; emmc_pinout.cmd = command->number; emmc_pinout.dat0 = data->number; @@ -853,9 +852,6 @@ static void emmc_claim_pins(const mcu_pin_obj_t *clock, const mcu_pin_obj_t *com continue; } claim_pin(pins[i]); - if (never_reset) { - never_reset_pin_number(pins[i]->number); - } s_claimed_pins[s_claimed_pin_count++] = pins[i]; } } @@ -950,7 +946,7 @@ emmcio_construct_result_t common_hal_emmcio_emmc_construct(emmcio_emmc_obj_t *se if (pin_err != EMMCIO_OK) { return pin_err; } - emmc_claim_pins(clock, command, data, reset, vccq, false); + emmc_claim_pins(clock, command, data, reset, vccq); emmcio_construct_result_t err = emmc_power_up(self, high_speed, detail); if (err != EMMCIO_OK) { @@ -989,7 +985,7 @@ mp_obj_t emmcio_automount_construct(const mcu_pin_obj_t *clock, const mcu_pin_ob if (emmc_check_pins(clock, command, data, reset, vccq, &detail) != EMMCIO_OK) { return MP_OBJ_NULL; } - emmc_claim_pins(clock, command, data, reset, vccq, true); + emmc_claim_pins(clock, command, data, reset, vccq); s_automount_obj.base.type = &emmcio_emmc_type; if (emmc_power_up(&s_automount_obj, high_speed, &detail) != EMMCIO_OK) { return MP_OBJ_NULL; diff --git a/ports/nordic/common-hal/microcontroller/Pin.c b/ports/nordic/common-hal/microcontroller/Pin.c index ea495d32893..95a4f4f0220 100644 --- a/ports/nordic/common-hal/microcontroller/Pin.c +++ b/ports/nordic/common-hal/microcontroller/Pin.c @@ -18,10 +18,9 @@ bool speaker_enable_in_use; // Bit mask of claimed pins on each of up to two ports. nrf52832 has one port; nrf52840 has two. static uint32_t claimed_pins[GPIO_COUNT]; -static uint32_t never_reset_pins[GPIO_COUNT]; +#ifdef SPEAKER_ENABLE_PIN static void reset_speaker_enable_pin(void) { - #ifdef SPEAKER_ENABLE_PIN speaker_enable_in_use = false; nrf_gpio_cfg(SPEAKER_ENABLE_PIN->number, NRF_GPIO_PIN_DIR_OUTPUT, @@ -30,33 +29,13 @@ static void reset_speaker_enable_pin(void) { NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE); nrf_gpio_pin_write(SPEAKER_ENABLE_PIN->number, false); - #endif } +#endif MP_WEAK bool board_reset_pin_number(uint8_t pin_number) { return false; } -void reset_all_pins(void) { - for (size_t i = 0; i < GPIO_COUNT; i++) { - claimed_pins[i] = never_reset_pins[i]; - } - - for (uint32_t pin = 0; pin < NUMBER_OF_PINS; ++pin) { - if ((never_reset_pins[nrf_pin_port(pin)] & (1 << nrf_relative_pin_number(pin))) != 0) { - continue; - } - // Allow the board to override the reset state of any pin. - if (board_reset_pin_number(pin)) { - continue; - } - nrf_gpio_cfg_default(pin); - } - - // After configuring SWD because it may be shared. - reset_speaker_enable_pin(); -} - // Mark pin as free and return it to a quiescent state. void reset_pin_number(uint8_t pin_number) { if (pin_number == NO_PIN) { @@ -65,7 +44,6 @@ void reset_pin_number(uint8_t pin_number) { // Clear claimed bit. claimed_pins[nrf_pin_port(pin_number)] &= ~(1 << nrf_relative_pin_number(pin_number)); - never_reset_pins[nrf_pin_port(pin_number)] &= ~(1 << nrf_relative_pin_number(pin_number)); #ifdef SPEAKER_ENABLE_PIN if (pin_number == SPEAKER_ENABLE_PIN->number) { @@ -78,17 +56,6 @@ void reset_pin_number(uint8_t pin_number) { } -void never_reset_pin_number(uint8_t pin_number) { - if (pin_number == NO_PIN) { - return; - } - never_reset_pins[nrf_pin_port(pin_number)] |= 1 << nrf_relative_pin_number(pin_number); -} - -void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - never_reset_pin_number(pin->number); -} - void common_hal_reset_pin(const mcu_pin_obj_t *pin) { if (pin == NULL) { return; @@ -96,9 +63,13 @@ void common_hal_reset_pin(const mcu_pin_obj_t *pin) { reset_pin_number(pin->number); } -void claim_pin(const mcu_pin_obj_t *pin) { +void claim_pin_number(uint8_t pin_number) { // Set bit in claimed_pins bitmask. - claimed_pins[nrf_pin_port(pin->number)] |= 1 << nrf_relative_pin_number(pin->number); + claimed_pins[nrf_pin_port(pin_number)] |= 1 << nrf_relative_pin_number(pin_number); +} + +void claim_pin(const mcu_pin_obj_t *pin) { + claim_pin_number(pin->number); #ifdef SPEAKER_ENABLE_PIN if (pin == SPEAKER_ENABLE_PIN) { @@ -136,10 +107,6 @@ uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin) { return pin->number; } -void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { - claim_pin(pin); -} - void common_hal_mcu_pin_reset_number(uint8_t pin_no) { reset_pin_number(pin_no); } diff --git a/ports/nordic/common-hal/microcontroller/Pin.h b/ports/nordic/common-hal/microcontroller/Pin.h index 330a8ef1283..b0b7b04ffdd 100644 --- a/ports/nordic/common-hal/microcontroller/Pin.h +++ b/ports/nordic/common-hal/microcontroller/Pin.h @@ -15,13 +15,15 @@ // pin numbers, `false` for others. bool board_reset_pin_number(uint8_t pin_number); -void reset_all_pins(void); +void common_hal_reset_pin(const mcu_pin_obj_t *pin); // reset_pin_number takes the pin number instead of the pointer so that objects don't // need to store a full pointer. void reset_pin_number(uint8_t pin); +void claim_pin_number(uint8_t pin); void claim_pin(const mcu_pin_obj_t *pin); bool pin_number_is_free(uint8_t pin_number); -void never_reset_pin_number(uint8_t pin_number); + +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); // Lower 5 bits of a pin number are the pin number in a port. // upper bits (just one bit for current chips) is port number. diff --git a/ports/nordic/common-hal/neopixel_write/__init__.c b/ports/nordic/common-hal/neopixel_write/__init__.c index e9f3a0fdacc..3fbf61af36f 100644 --- a/ports/nordic/common-hal/neopixel_write/__init__.c +++ b/ports/nordic/common-hal/neopixel_write/__init__.c @@ -82,7 +82,10 @@ static NRF_PWM_Type *find_free_pwm(void) { } static size_t pixels_pattern_heap_size = 0; -// Called during reset_port() to free the pattern buffer +// Clear the root pointer during reset_port() while the old GC heap (and the pattern +// buffer in it) is still alive. Root pointers are NOT zeroed between VM runs, so +// without this the pointer would dangle after the heap is torn down and re-created. +// The now-unreferenced block is reclaimed by the final sweep in gc_deinit(). void neopixel_write_reset(void) { MP_STATE_VM(pixels_pattern_heap) = NULL; pixels_pattern_heap_size = 0; diff --git a/ports/nordic/common-hal/paralleldisplaybus/ParallelBus.c b/ports/nordic/common-hal/paralleldisplaybus/ParallelBus.c index 5377d020f6b..cb040e82760 100644 --- a/ports/nordic/common-hal/paralleldisplaybus/ParallelBus.c +++ b/ports/nordic/common-hal/paralleldisplaybus/ParallelBus.c @@ -35,6 +35,9 @@ void common_hal_paralleldisplaybus_parallelbus_construct(paralleldisplaybus_para g = NRF_P1; num_pins_in_port = P1_PIN_NUM; } + for (uint8_t i = 0; i < 8; i++) { + claim_pin_number(data_pin + i); + } g->DIRSET = 0xff << (data_pin % num_pins_in_port); for (uint8_t i = 0; i < 8; i++) { g->PIN_CNF[data_pin + i] |= NRF_GPIO_PIN_S0S1 << GPIO_PIN_CNF_DRIVE_Pos; @@ -73,17 +76,8 @@ void common_hal_paralleldisplaybus_parallelbus_construct(paralleldisplaybus_para self->reset.base.type = &digitalio_digitalinout_type; common_hal_digitalio_digitalinout_construct(&self->reset, reset); common_hal_digitalio_digitalinout_switch_to_output(&self->reset, true, DRIVE_MODE_PUSH_PULL); - never_reset_pin_number(reset->number); common_hal_paralleldisplaybus_parallelbus_reset(self); } - - never_reset_pin_number(command->number); - never_reset_pin_number(chip_select->number); - never_reset_pin_number(write->number); - never_reset_pin_number(read->number); - for (uint8_t i = 0; i < 8; i++) { - never_reset_pin_number(data_pin + i); - } } void common_hal_paralleldisplaybus_parallelbus_deinit(paralleldisplaybus_parallelbus_obj_t *self) { diff --git a/ports/nordic/common-hal/pwmio/PWMOut.c b/ports/nordic/common-hal/pwmio/PWMOut.c index 6dc7ff56c71..40e613476a2 100644 --- a/ports/nordic/common-hal/pwmio/PWMOut.c +++ b/ports/nordic/common-hal/pwmio/PWMOut.c @@ -34,8 +34,6 @@ static NRF_PWM_Type *pwms[] = { static uint16_t pwm_seq[MP_ARRAY_SIZE(pwms)][CHANNELS_PER_PWM]; -static uint8_t never_reset_pwm[MP_ARRAY_SIZE(pwms)]; - static int pwm_idx(NRF_PWM_Type *pwm) { for (size_t i = 0; i < MP_ARRAY_SIZE(pwms); i++) { if (pwms[i] == pwm) { @@ -45,12 +43,6 @@ static int pwm_idx(NRF_PWM_Type *pwm) { return -1; } -void common_hal_pwmio_pwmout_never_reset(pwmio_pwmout_obj_t *self) { - never_reset_pwm[pwm_idx(self->pwm)] |= 1 << self->channel; - - common_hal_never_reset_pin(self->pin); -} - static void reset_single_pwmout(uint8_t i) { NRF_PWM_Type *pwm = pwms[i]; @@ -226,8 +218,6 @@ void common_hal_pwmio_pwmout_deinit(pwmio_pwmout_obj_t *self) { nrf_gpio_cfg_default(self->pin->number); - never_reset_pwm[pwm_idx(self->pwm)] &= ~(1 << self->channel); - NRF_PWM_Type *pwm = self->pwm; self->pwm = NULL; diff --git a/ports/nordic/common-hal/rgbmatrix/RGBMatrix.c b/ports/nordic/common-hal/rgbmatrix/RGBMatrix.c index 01f2cbc737e..b5b8c52bd6d 100644 --- a/ports/nordic/common-hal/rgbmatrix/RGBMatrix.c +++ b/ports/nordic/common-hal/rgbmatrix/RGBMatrix.c @@ -14,7 +14,6 @@ extern void _PM_IRQ_HANDLER(void); void *common_hal_rgbmatrix_timer_allocate(rgbmatrix_rgbmatrix_obj_t *self) { nrfx_timer_t *timer = nrf_peripherals_allocate_timer_or_throw(); - nrf_peripherals_timer_never_reset(timer); return timer->p_reg; } diff --git a/ports/nordic/common-hal/rtc/RTC.h b/ports/nordic/common-hal/rtc/RTC.h index 18625763562..d597327fd23 100644 --- a/ports/nordic/common-hal/rtc/RTC.h +++ b/ports/nordic/common-hal/rtc/RTC.h @@ -7,5 +7,4 @@ #pragma once extern void rtc_init(void); -extern void rtc_reset(void); extern void common_hal_rtc_init(void); diff --git a/ports/nordic/mpconfigport.mk b/ports/nordic/mpconfigport.mk index a9299ee60aa..3f762b4eb68 100644 --- a/ports/nordic/mpconfigport.mk +++ b/ports/nordic/mpconfigport.mk @@ -1,5 +1,8 @@ # All linking can be done with this common templated linker script, which has # parameters that vary based on chip and/or board. +# Properties get a dedicated linker section here, so they can drop the unused slots. +CIRCUITPY_OPTIMIZE_PROPERTY_FLASH_SIZE ?= 1 + LD_TEMPLATE_FILE = boards/common.template.ld INTERNAL_LIBM = 1 @@ -56,6 +59,7 @@ CIRCUITPY_BLE_FILE_SERVICE ?= 1 CIRCUITPY_BLE_SERIAL_SERVICE ?= 1 CIRCUITPY_COMPUTED_GOTO_SAVE_SPACE ?= 1 +CIRCUITPY_LOAD_NATIVE ?= 1 # nRF52840-specific @@ -92,6 +96,7 @@ ifeq ($(INTERNAL_FLASH_FILESYSTEM),1) OPTIMIZATION_FLAGS ?= -Os CIRCUITPY_LTO = 1 CIRCUITPY_LTO_PARTITION = balanced + CIRCUITPY_ULAB_OPTIMIZE_SIZE ?= 1 else CIRCUITPY_USB_AUDIO ?= 1 endif @@ -129,3 +134,6 @@ ifneq ($(CIRCUITPY_BLEIO_NATIVE),1) CIRCUITPY_BLE_FILE_SERVICE = 0 CIRCUITPY_BLE_SERIAL_SERVICE = 0 endif + +# Cleanup is performed by GC finalizers (__del__ -> deinit) instead of a bulk pin reset. +CIRCUITPY_BULK_RESET = 0 diff --git a/ports/nordic/peripherals/nrf/timers.c b/ports/nordic/peripherals/nrf/timers.c index ec1bde65877..c570bec65f5 100644 --- a/ports/nordic/peripherals/nrf/timers.c +++ b/ports/nordic/peripherals/nrf/timers.c @@ -35,27 +35,6 @@ static nrfx_timer_t nrfx_timers[] = { }; static bool nrfx_timer_allocated[ARRAY_SIZE(nrfx_timers)]; -static bool nrfx_timer_never_reset[ARRAY_SIZE(nrfx_timers)]; - -void timers_reset(void) { - for (size_t i = 0; i < ARRAY_SIZE(nrfx_timers); i++) { - if (nrfx_timer_never_reset[i]) { - continue; - } - nrfx_timer_uninit(&nrfx_timers[i]); - nrfx_timer_allocated[i] = false; - } -} - -void nrf_peripherals_timer_never_reset(nrfx_timer_t *timer) { - int idx = nrf_peripherals_timer_idx_from_timer(timer); - nrfx_timer_never_reset[idx] = true; -} - -void nrf_peripherals_timer_reset_ok(nrfx_timer_t *timer) { - int idx = nrf_peripherals_timer_idx_from_timer(timer); - nrfx_timer_never_reset[idx] = false; -} nrfx_timer_t *nrf_peripherals_timer_from_reg(NRF_TIMER_Type *ptr) { for (size_t i = 0; i < ARRAY_SIZE(nrfx_timers); i++) { @@ -102,7 +81,6 @@ void nrf_peripherals_free_timer(nrfx_timer_t *timer) { size_t idx = nrf_peripherals_timer_idx_from_timer(timer); if (idx != ~(size_t)0) { nrfx_timer_allocated[idx] = false; - nrfx_timer_never_reset[idx] = false; // Safe to call even if not initialized. nrfx_timer_uninit(timer); } diff --git a/ports/nordic/peripherals/nrf/timers.h b/ports/nordic/peripherals/nrf/timers.h index 44b8bc1bf04..ea6022ffd72 100644 --- a/ports/nordic/peripherals/nrf/timers.h +++ b/ports/nordic/peripherals/nrf/timers.h @@ -9,11 +9,8 @@ #include "nrfx.h" #include "nrfx_timer.h" -void timers_reset(void); nrfx_timer_t *nrf_peripherals_allocate_timer(void); nrfx_timer_t *nrf_peripherals_allocate_timer_or_throw(void); void nrf_peripherals_free_timer(nrfx_timer_t *timer); -void nrf_peripherals_timer_never_reset(nrfx_timer_t *timer); -void nrf_peripherals_timer_reset_ok(nrfx_timer_t *timer); nrfx_timer_t *nrf_peripherals_timer_from_reg(NRF_TIMER_Type *ptr); size_t nrf_peripherals_timer_idx_from_timer(nrfx_timer_t *ptr); diff --git a/ports/nordic/supervisor/port.c b/ports/nordic/supervisor/port.c index 6c3897db457..961d16c00f3 100644 --- a/ports/nordic/supervisor/port.c +++ b/ports/nordic/supervisor/port.c @@ -202,37 +202,32 @@ safe_mode_t port_init(void) { } void reset_port(void) { - #if CIRCUITPY_BUSIO - uart_reset(); + #if CIRCUITPY_RTC + rtc_reset(); #endif #if CIRCUITPY_NEOPIXEL_WRITE + // Must run while the old GC heap is still alive so the pattern buffer is + // reclaimed instead of left dangling behind the root pointer. neopixel_write_reset(); #endif - #if CIRCUITPY_AUDIOBUSIO - i2s_reset(); - #endif - - #if CIRCUITPY_RTC - rtc_reset(); - #endif - #if CIRCUITPY_EMMCIO emmcio_reset(); #endif - timers_reset(); - #if CIRCUITPY_WATCHDOG watchdog_reset(); #endif - // Always reset GPIOTE because it is shared. - if (nrfx_gpiote_is_init()) { - nrfx_gpiote_uninit(); + // GPIOTE is shared by every object that needs an event channel (pulseio, + // countio, rotaryio, max3421e, alarm pins). Initialize it once and never + // tear it down here: each object uninit's its own channel in deinit + // (via __del__), and alarm pins are released by alarm_reset() in main.c + // before reset_port() runs. + if (!nrfx_gpiote_is_init()) { + nrfx_gpiote_init(NRFX_GPIOTE_CONFIG_IRQ_PRIORITY); } - nrfx_gpiote_init(NRFX_GPIOTE_CONFIG_IRQ_PRIORITY); } void reset_to_bootloader(void) { diff --git a/ports/raspberrypi/Makefile b/ports/raspberrypi/Makefile index 5512c7c9f47..f961664a081 100755 --- a/ports/raspberrypi/Makefile +++ b/ports/raspberrypi/Makefile @@ -217,7 +217,14 @@ CFLAGS += \ -DPICO_NO_BI_BOOTSEL_VIA_DOUBLE_RESET=0 \ -DDVI_1BPP_BIT_REVERSE=0 -OPTIMIZATION_FLAGS ?= -O3 +# Shared IRQ handlers, counted over all IRQs: USB (2), CYW43, audio, rp2pio, PinAlarm and +# max3421e. The SDK default of 4 is too few on Pico W boards. +CFLAGS += -DPICO_MAX_SHARED_IRQ_HANDLERS=8 + +# -O2 plus the two -O3 flags that measure: -funswitch-loops (16 KB) carries the sprite +# blits, -fvect-cost-model=dynamic (4 KB) the fills and memcpy. Same speed as -O3 on both +# cores for about 125 KB less flash. Boards may override this in mpconfigboard.mk. +OPTIMIZATION_FLAGS ?= -O2 -funswitch-loops -fvect-cost-model=dynamic # TinyUSB defines CFLAGS += \ -DCFG_TUSB_OS=OPT_OS_PICO \ @@ -479,12 +486,17 @@ UF2_ID = 0xe48bff59 # Double coprocessor is only available on the ARM core. DOUBLE_EABI = dcp INC += \ - -isystem sdk/src/rp2_common/hardware_dcp/include/ + -isystem sdk/src/rp2_common/hardware_dcp/include/ \ + -isystem sdk/src/rp2_common/hardware_rosc/include/ \ + -isystem sdk/src/rp2_common/pico_low_power/include/ CFLAGS += -DPICO_RP2040=0 -DPICO_RP2350=1 SRC_SDK_CHIP_VARIANT := \ + src/common/pico_util/fixed_bitset.c \ src/rp2_common/hardware_powman/powman.c \ + src/rp2_common/hardware_rosc/rosc.c \ + src/rp2_common/pico_low_power/low_power.c \ SRC_S_UPPER_CHIP_VARIANT := \ sdk/src/rp2_common/pico_double/double_conv_m33.S \ @@ -571,6 +583,7 @@ $(patsubst %.c,$(BUILD)/%.o,$(SRC_SDK) $(SRC_CYW43)): CFLAGS += -Wno-missing-pro SRC_C += \ boards/$(BOARD)/board.c \ boards/$(BOARD)/pins.c \ + common-hal/rtc/pico_time.c \ bindings/rp2pio/StateMachine.c \ bindings/rp2pio/__init__.c \ common-hal/rp2pio/StateMachine.c \ @@ -725,8 +738,10 @@ CFLAGS += -I$(MBEDTLS_GEN) $(patsubst %.c,$(BUILD)/%.o,$(SRC_MBEDTLS)) $(OBJ_MBEDTLS): $(MBEDTLS_WRAPPERS_H) # Vendored third-party code, so don't hold it to CircuitPython's warning settings. The # unused-but-set variables are real but harmless: they are in key-exchange paths that -# are compiled out. +# are compiled out. gcc 15.3 reports false array-bounds errors in the inlined +# mbedtls_xor_no_simd() in gcm.c and aes.c on RP2350. $(patsubst %.c,$(BUILD)/%.o,$(SRC_MBEDTLS)) $(OBJ_MBEDTLS): CFLAGS += \ + -Wno-array-bounds \ -Wno-suggest-attribute=format \ -Wno-unused-but-set-variable else @@ -748,11 +763,10 @@ endif $(patsubst %.S,$(BUILD)/%.o,$(SRC_S_UPPER)): CFLAGS += -Wno-undef -# Derive the C++ flags from the C flags (mirroring the unix port) so the -# vendored C++ translation unit picks up all the SDK include paths and defines. -# Strip the C-only options that g++ rejects, and disable exceptions/RTTI to -# match the firmware's constraints. -CXXFLAGS += $(filter-out -std=gnu11 -Werror=missing-prototypes -Wold-style-definition -Wstrict-prototypes -Werror-implicit-function-declaration -Wnested-externs,$(CFLAGS)) -std=gnu++17 -fno-exceptions -fno-rtti +# py/mkrules.mk derives CXXFLAGS from CFLAGS, which gives the vendored C++ +# translation unit the SDK include paths and defines. Only the C++-specific +# options belong here. +CXXFLAGS += -std=gnu++17 -fno-exceptions -fno-rtti OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_SDK:.c=.o)) diff --git a/ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.mk b/ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.mk index 8c46cc0713b..76dd0da5f40 100644 --- a/ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.mk +++ b/ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.mk @@ -16,3 +16,11 @@ CIRCUITPY_PICOGAME_FRAMEBUFFER = 1 # CIRCUITPY_DISPLAY_FONT = $(TOP)/tools/fonts/unifont-16.0.02-all.bdf # CIRCUITPY_FONT_EXTRA_CHARACTERS = "🖮🖱️" + +# USB video (UVC) cameras have configuration descriptors of several KB. +# TinyUSB skips enumerating any device whose descriptor doesn't fit here. +CFLAGS += -DCFG_TUH_ENUMERATION_BUFSIZE=4096 +# Enable host isochronous IN support (UVC video, UAC audio). The packet ring +# is only allocated when CIRCUITPY_USB_HOST_ISO_BUFFER_SIZE is set in +# settings.toml. +CFLAGS += -DPIO_USB_HOST_ISOCHRONOUS=1 diff --git a/ports/raspberrypi/boards/adafruit_metro_rp2350/mpconfigboard.mk b/ports/raspberrypi/boards/adafruit_metro_rp2350/mpconfigboard.mk index 6d499c367d7..311869c1eac 100644 --- a/ports/raspberrypi/boards/adafruit_metro_rp2350/mpconfigboard.mk +++ b/ports/raspberrypi/boards/adafruit_metro_rp2350/mpconfigboard.mk @@ -10,3 +10,7 @@ CHIP_FAMILY = rp2 EXTERNAL_FLASH_DEVICES = "W25Q128JVxQ" CIRCUITPY_SDIOIO = 1 + +CIRCUITPY_PICOGAME = 1 +CIRCUITPY_PICOGAME_FRAMEBUFFER = 1 +CIRCUITPY_PICOGAME_FAST_DISPLAY = 1 diff --git a/ports/raspberrypi/boards/studiolab_picoexpander/board.c b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/board.c similarity index 100% rename from ports/raspberrypi/boards/studiolab_picoexpander/board.c rename to ports/raspberrypi/boards/ide_tudelft_realpicoexpander/board.c diff --git a/ports/raspberrypi/boards/studiolab_picoexpander/link.ld b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/link.ld similarity index 100% rename from ports/raspberrypi/boards/studiolab_picoexpander/link.ld rename to ports/raspberrypi/boards/ide_tudelft_realpicoexpander/link.ld diff --git a/ports/raspberrypi/boards/studiolab_picoexpander/mpconfigboard.h b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/mpconfigboard.h similarity index 91% rename from ports/raspberrypi/boards/studiolab_picoexpander/mpconfigboard.h rename to ports/raspberrypi/boards/ide_tudelft_realpicoexpander/mpconfigboard.h index ea475f68222..554747cdd4a 100644 --- a/ports/raspberrypi/boards/studiolab_picoexpander/mpconfigboard.h +++ b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/mpconfigboard.h @@ -4,7 +4,7 @@ // // SPDX-License-Identifier: MIT -#define MICROPY_HW_BOARD_NAME "Studiolab Pico Expander" +#define MICROPY_HW_BOARD_NAME "IDE-TUDelft RealPicoExpander" #define MICROPY_HW_MCU_NAME "rp2350a" #define CIRCUITPY_DIGITALIO_HAVE_INVALID_PULL (1) diff --git a/ports/raspberrypi/boards/studiolab_picoexpander/mpconfigboard.mk b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/mpconfigboard.mk similarity index 86% rename from ports/raspberrypi/boards/studiolab_picoexpander/mpconfigboard.mk rename to ports/raspberrypi/boards/ide_tudelft_realpicoexpander/mpconfigboard.mk index 7214b36da28..d08742d0ef8 100644 --- a/ports/raspberrypi/boards/studiolab_picoexpander/mpconfigboard.mk +++ b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/mpconfigboard.mk @@ -1,13 +1,13 @@ USB_VID = 0x1209 USB_PID = 0xC1C1 -USB_PRODUCT = "StudioLab Pico Expander" -USB_MANUFACTURER = "Raspberry Pi" +USB_PRODUCT = "IDE-TUDelft RealPicoExpander" +USB_MANUFACTURER = "StudioLab TUDelft" CHIP_VARIANT = RP2350 CHIP_PACKAGE = A CHIP_FAMILY = rp2 -EXTERNAL_FLASH_DEVICES = "W25Q32JVxQ" +EXTERNAL_FLASH_DEVICES = "W25Q64JVxQ" CIRCUITPY__EVE = 1 diff --git a/ports/raspberrypi/boards/studiolab_picoexpander/pico-sdk-configboard.h b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/pico-sdk-configboard.h similarity index 100% rename from ports/raspberrypi/boards/studiolab_picoexpander/pico-sdk-configboard.h rename to ports/raspberrypi/boards/ide_tudelft_realpicoexpander/pico-sdk-configboard.h diff --git a/ports/raspberrypi/boards/studiolab_picoexpander/pins.c b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/pins.c similarity index 95% rename from ports/raspberrypi/boards/studiolab_picoexpander/pins.c rename to ports/raspberrypi/boards/ide_tudelft_realpicoexpander/pins.c index 6faafbfd177..708aa24a376 100644 --- a/ports/raspberrypi/boards/studiolab_picoexpander/pins.c +++ b/ports/raspberrypi/boards/ide_tudelft_realpicoexpander/pins.c @@ -61,15 +61,19 @@ static const mp_rom_map_elem_t board_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO15) }, { MP_ROM_QSTR(MP_QSTR_GP15), MP_ROM_PTR(&pin_GPIO15) }, + { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO16) }, { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO16) }, { MP_ROM_QSTR(MP_QSTR_GP16), MP_ROM_PTR(&pin_GPIO16) }, + { MP_ROM_QSTR(MP_QSTR_CS), MP_ROM_PTR(&pin_GPIO17) }, { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO17) }, { MP_ROM_QSTR(MP_QSTR_GP17), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO18) }, { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO18) }, { MP_ROM_QSTR(MP_QSTR_GP18), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO19) }, { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO19) }, { MP_ROM_QSTR(MP_QSTR_GP19), MP_ROM_PTR(&pin_GPIO19) }, diff --git a/ports/raspberrypi/boards/pajenicko_picopad/mpconfigboard.mk b/ports/raspberrypi/boards/pajenicko_picopad/mpconfigboard.mk index b5df0e6ecb5..03a203dc2de 100644 --- a/ports/raspberrypi/boards/pajenicko_picopad/mpconfigboard.mk +++ b/ports/raspberrypi/boards/pajenicko_picopad/mpconfigboard.mk @@ -50,8 +50,3 @@ CFLAGS += \ # Must be accompanied by a linker script change CFLAGS += -DCIRCUITPY_FIRMWARE_SIZE='(1536 * 1024)' - -# The rp2 port default is -O3; on this Cortex-M0+ (no SIMD/FPU, 16 KB XIP cache) -O2 plus -# these five loop passes measures within +-1% of -O3 across the picogame render kernels -# while using ~150 KB less flash (gc.o/vm.o stay -O3 via SUPEROPT regardless). -OPTIMIZATION_FLAGS = -O2 -funswitch-loops -fpredictive-commoning -fgcse-after-reload -ftree-partial-pre -fsplit-paths diff --git a/ports/raspberrypi/boards/pimoroni_picosystem/mpconfigboard.mk b/ports/raspberrypi/boards/pimoroni_picosystem/mpconfigboard.mk index 8dca786a5e0..38ee11df70b 100644 --- a/ports/raspberrypi/boards/pimoroni_picosystem/mpconfigboard.mk +++ b/ports/raspberrypi/boards/pimoroni_picosystem/mpconfigboard.mk @@ -12,6 +12,8 @@ CIRCUITPY__EVE = 1 CIRCUITPY_KEYPAD = 1 CIRCUITPY_STAGE = 1 +CIRCUITPY_PICOGAME = 1 +CIRCUITPY_PICOGAME_FAST_DISPLAY = 1 CIRCUITPY_AUDIOIO = 1 diff --git a/ports/raspberrypi/boards/raspberry_pi_pico/mpconfigboard.mk b/ports/raspberrypi/boards/raspberry_pi_pico/mpconfigboard.mk index 20607d50e41..c53ddbbd7e4 100644 --- a/ports/raspberrypi/boards/raspberry_pi_pico/mpconfigboard.mk +++ b/ports/raspberrypi/boards/raspberry_pi_pico/mpconfigboard.mk @@ -9,4 +9,8 @@ CHIP_FAMILY = rp2 EXTERNAL_FLASH_DEVICES = "W25Q16JVxQ" CIRCUITPY__EVE = 1 + +CIRCUITPY_PICOGAME = 1 +CIRCUITPY_PICOGAME_FAST_DISPLAY = 1 + CIRCUITPY_PICODVI = 1 diff --git a/ports/raspberrypi/boards/raspberry_pi_pico_w/mpconfigboard.mk b/ports/raspberrypi/boards/raspberry_pi_pico_w/mpconfigboard.mk index e658db65f4d..bb7a9208899 100644 --- a/ports/raspberrypi/boards/raspberry_pi_pico_w/mpconfigboard.mk +++ b/ports/raspberrypi/boards/raspberry_pi_pico_w/mpconfigboard.mk @@ -12,6 +12,9 @@ CIRCUITPY_USB_HOST = 0 CIRCUITPY__EVE = 1 +CIRCUITPY_PICOGAME = 1 +CIRCUITPY_PICOGAME_FAST_DISPLAY = 1 + CIRCUITPY_CYW43 = 1 CIRCUITPY_SSL = 1 CIRCUITPY_HASHLIB = 1 diff --git a/ports/renode/boards/renode_cortex_m0plus/board.c b/ports/raspberrypi/boards/securinghw-xiaomao/board.c similarity index 100% rename from ports/renode/boards/renode_cortex_m0plus/board.c rename to ports/raspberrypi/boards/securinghw-xiaomao/board.c diff --git a/ports/raspberrypi/boards/securinghw-xiaomao/mpconfigboard.h b/ports/raspberrypi/boards/securinghw-xiaomao/mpconfigboard.h new file mode 100644 index 00000000000..ed69167c774 --- /dev/null +++ b/ports/raspberrypi/boards/securinghw-xiaomao/mpconfigboard.h @@ -0,0 +1,17 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#define MICROPY_HW_BOARD_NAME "SecuringHardware.com Xiaomao" +#define MICROPY_HW_MCU_NAME "rp2040" + +#define MICROPY_HW_NEOPIXEL (&pin_GPIO7) +#define CIRCUITPY_STATUS_LED_POWER (&pin_GPIO6) + +#define CIRCUITPY_BOARD_UART (2) +#define CIRCUITPY_BOARD_UART_PIN {{.tx = &pin_GPIO16, .rx = &pin_GPIO17}, \ + {.tx = &pin_GPIO20, .rx = &pin_GPIO21}} diff --git a/ports/raspberrypi/boards/securinghw-xiaomao/mpconfigboard.mk b/ports/raspberrypi/boards/securinghw-xiaomao/mpconfigboard.mk new file mode 100644 index 00000000000..19b8639883a --- /dev/null +++ b/ports/raspberrypi/boards/securinghw-xiaomao/mpconfigboard.mk @@ -0,0 +1,9 @@ +USB_VID = 0x2E8A +USB_PID = 0x1116 +USB_PRODUCT = "Xiaomao" +USB_MANUFACTURER = "SecuringHardwarecom" + +CHIP_VARIANT = RP2040 +CHIP_FAMILY = rp2 + +EXTERNAL_FLASH_DEVICES = "W25Q128JVxQ" diff --git a/ports/broadcom/common-hal/microcontroller/__init__.h b/ports/raspberrypi/boards/securinghw-xiaomao/pico-sdk-configboard.h similarity index 75% rename from ports/broadcom/common-hal/microcontroller/__init__.h rename to ports/raspberrypi/boards/securinghw-xiaomao/pico-sdk-configboard.h index 217a3de3c7a..110195b7794 100644 --- a/ports/broadcom/common-hal/microcontroller/__init__.h +++ b/ports/raspberrypi/boards/securinghw-xiaomao/pico-sdk-configboard.h @@ -6,4 +6,4 @@ #pragma once -#define TOTAL_GPIO_COUNT 58 +// Put board-specific pico-sdk definitions here. This file must exist. diff --git a/ports/raspberrypi/boards/securinghw-xiaomao/pins.c b/ports/raspberrypi/boards/securinghw-xiaomao/pins.c new file mode 100644 index 00000000000..a6b6cd3ac72 --- /dev/null +++ b/ports/raspberrypi/boards/securinghw-xiaomao/pins.c @@ -0,0 +1,37 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/board/__init__.h" + +CIRCUITPY_BOARD_BUS_SINGLETON(uart1, uart, 1) + +static const mp_rom_map_elem_t board_module_globals_table[] = { + CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS + + { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO0) }, + { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO16) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_PTR(&pin_GPIO19) }, + { MP_ROM_QSTR(MP_QSTR_TX1), MP_ROM_PTR(&pin_GPIO20) }, + { MP_ROM_QSTR(MP_QSTR_RX1), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_CTS1), MP_ROM_PTR(&pin_GPIO22) }, + { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO27) }, + { MP_ROM_QSTR(MP_QSTR_BTN), MP_ROM_PTR(&pin_GPIO28) }, + { MP_ROM_QSTR(MP_QSTR_VBUS_DET), MP_ROM_PTR(&pin_GPIO29) }, + + { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_NEOPIXEL_POWER), MP_ROM_PTR(&pin_GPIO6) }, + + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, + { MP_ROM_QSTR(MP_QSTR_UART1), MP_ROM_PTR(&board_uart1_obj) }, +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/raspberrypi/common-hal/alarm/__init__.c b/ports/raspberrypi/common-hal/alarm/__init__.c index 39ad8ca1fe6..c25f904e34f 100644 --- a/ports/raspberrypi/common-hal/alarm/__init__.c +++ b/ports/raspberrypi/common-hal/alarm/__init__.c @@ -34,16 +34,21 @@ #include "hardware/structs/watchdog.h" // XOSC shutdown -#include "hardware/rtc.h" #include "hardware/pll.h" #include "hardware/regs/io_bank0.h" +#if PICO_RP2350 +#include "hardware/powman.h" +#include "pico/low_power.h" +#endif + // Watchdog scratch register // Not used elsewhere in the SDK for now, keep an eye on it #define RP_WKUP_SCRATCH_REG 0 // Light sleep turns off nonvolatile Busio and other wake-only peripherals // TODO: this only saves about 2mA right now, expand with other non-essentials +#if PICO_RP2040 const uint32_t RP_LIGHTSLEEP_EN0_MASK = ~( CLOCKS_SLEEP_EN0_CLK_SYS_SPI1_BITS | CLOCKS_SLEEP_EN0_CLK_PERI_SPI1_BITS | @@ -59,7 +64,28 @@ const uint32_t RP_LIGHTSLEEP_EN0_MASK = ~( ); // This bank has the USB clocks in it, leave it for now const uint32_t RP_LIGHTSLEEP_EN1_MASK = CLOCKS_SLEEP_EN1_RESET; +#else +// Same peripherals as RP2040. RP2350 adds PIO2 and moves SPI to the second bank. +const uint32_t RP_LIGHTSLEEP_EN0_MASK = ~( + CLOCKS_SLEEP_EN0_CLK_SYS_PWM_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_PIO2_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_PIO1_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_PIO0_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_I2C1_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_I2C0_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_ADC_BITS | + CLOCKS_SLEEP_EN0_CLK_ADC_BITS + ); +// This bank has the USB clocks in it, only turn off SPI +const uint32_t RP_LIGHTSLEEP_EN1_MASK = CLOCKS_SLEEP_EN1_RESET & ~( + CLOCKS_SLEEP_EN1_CLK_SYS_SPI1_BITS | + CLOCKS_SLEEP_EN1_CLK_PERI_SPI1_BITS | + CLOCKS_SLEEP_EN1_CLK_SYS_SPI0_BITS | + CLOCKS_SLEEP_EN1_CLK_PERI_SPI0_BITS + ); +#endif +#if PICO_RP2040 // Light sleeps used for TimeAlarm deep sleep turn off almost everything const uint32_t RP_LIGHTSLEEP_EN0_MASK_HARSH = ( CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS | @@ -68,6 +94,7 @@ const uint32_t RP_LIGHTSLEEP_EN0_MASK_HARSH = ( const uint32_t RP_LIGHTSLEEP_EN1_MASK_HARSH = 0x0; static void prepare_for_dormant_xosc(void); +#endif // Singleton instance of SleepMemory. const alarm_sleep_memory_obj_t alarm_sleep_memory_obj = { @@ -80,6 +107,19 @@ const alarm_sleep_memory_obj_t alarm_sleep_memory_obj = { // This object lives across VM instantiations, so none of these objects can contain references to the heap. alarm_wake_alarm_union_t alarm_wake_alarm; +#if PICO_RP2350 +// powman remembers what caused the last power up until the next one. Only report +// it until the first alarm_reset() after boot. +static bool powman_wakeup_reported; + +bool alarm_woke_from_powman(void) { + // powman is not reset by a watchdog reboot, so its registers would be stale. + return (powman_hw->chip_reset & POWMAN_CHIP_RESET_HAD_SWCORE_PD_BITS) && + !watchdog_caused_reboot() && + (powman_hw->last_swcore_pwrup & (RP_POWMAN_PWRUP_GPIO_BITS | RP_POWMAN_PWRUP_ALARM_BITS)); +} +#endif + void alarm_reset(void) { alarm_sleep_memory_reset(); alarm_pin_pinalarm_reset(); @@ -87,6 +127,9 @@ void alarm_reset(void) { // Reset the scratch source watchdog_hw->scratch[RP_WKUP_SCRATCH_REG] = RP_SLEEP_WAKEUP_UNDEF; + #if PICO_RP2350 + powman_wakeup_reported = true; + #endif } static uint8_t _get_wakeup_cause(void) { @@ -102,6 +145,14 @@ static uint8_t _get_wakeup_cause(void) { if (watchdog_hw->scratch[RP_WKUP_SCRATCH_REG] != RP_SLEEP_WAKEUP_UNDEF) { return watchdog_hw->scratch[RP_WKUP_SCRATCH_REG]; } + #if PICO_RP2350 + if (!powman_wakeup_reported && alarm_woke_from_powman()) { + if (powman_hw->last_swcore_pwrup & RP_POWMAN_PWRUP_ALARM_BITS) { + return RP_SLEEP_WAKEUP_RTC; + } + return RP_SLEEP_WAKEUP_GPIO; + } + #endif return RP_SLEEP_WAKEUP_UNDEF; } @@ -172,7 +223,7 @@ mp_obj_t common_hal_alarm_light_sleep_until_alarms(size_t n_alarms, const mp_obj clocks_hw->sleep_en1 = RP_LIGHTSLEEP_EN1_MASK; // Enable System Control Block (SCB) deep sleep - scb_hw->scr |= M0PLUS_SCR_SLEEPDEEP_BITS; + scb_hw->scr |= ARM_CPU_PREFIXED(SCR_SLEEPDEEP_BITS); __wfi(); } @@ -194,7 +245,41 @@ void common_hal_alarm_set_deep_sleep_alarms(size_t n_alarms, const mp_obj_t *ala _setup_sleep_alarms(true, n_alarms, alarms); } +#if PICO_RP2350 +// Power down with powman. Waking up reboots into main(), so this does not return. +static void MP_NORETURN rp2350_enter_deep_sleep(void) { + #if CIRCUITPY_CYW43 + cyw43_enter_deep_sleep(); + #endif + + // pico_low_power arms one wakeup per call and leaves the others alone, so + // arm every pin alarm first. + alarm_pin_pinalarm_enable_powman_wakeups(); + + // A NULL power state keeps only the SRAM that holds .persistent_data powered. + // That is where alarm.sleep_memory lives. + uint pin_number; + bool edge; + bool value; + if (!alarm_time_timealarm_is_set() && + alarm_pin_pinalarm_first_powman_wakeup(&pin_number, &edge, &value)) { + low_power_pstate_until_gpio_pin_state(pin_number, edge, value, NULL, NULL); + } else { + // With no alarm at all, sleep until a time that never comes. + uint64_t wakeup_ms = alarm_time_timealarm_is_set() ? alarm_time_timealarm_get_wakeup_ms() : INT64_MAX / 1000; + low_power_pstate_until_aon_timer(from_us_since_boot(wakeup_ms * 1000), NULL, NULL); + } + + // Could not power down, most likely because an alarm already fired. + watchdog_hw->scratch[RP_WKUP_SCRATCH_REG] = _get_wakeup_cause(); + reset_cpu(); +} +#endif + void MP_NORETURN common_hal_alarm_enter_deep_sleep(void) { + #if PICO_RP2350 + rp2350_enter_deep_sleep(); + #else bool timealarm_set = alarm_time_timealarm_is_set(); #if CIRCUITPY_CYW43 @@ -223,12 +308,14 @@ void MP_NORETURN common_hal_alarm_enter_deep_sleep(void) { // Just before reset, enable the pinalarm interrupt. alarm_pin_pinalarm_entering_deep_sleep(); reset_cpu(); + #endif } void common_hal_alarm_gc_collect(void) { gc_collect_ptr(shared_alarm_get_wake_alarm()); } +#if PICO_RP2040 static void prepare_for_dormant_xosc(void) { // TODO: add ROSC support with sleep_run_from_dormant_source when it's added to SDK uint src_hz = XOSC_MHZ * MHZ; @@ -259,3 +346,4 @@ static void prepare_for_dormant_xosc(void) { pll_deinit(pll_sys); pll_deinit(pll_usb); } +#endif diff --git a/ports/raspberrypi/common-hal/alarm/__init__.h b/ports/raspberrypi/common-hal/alarm/__init__.h index 49e06a4c323..1ef985d9e79 100644 --- a/ports/raspberrypi/common-hal/alarm/__init__.h +++ b/ports/raspberrypi/common-hal/alarm/__init__.h @@ -16,6 +16,18 @@ #define RP_SLEEP_WAKEUP_GPIO 1 #define RP_SLEEP_WAKEUP_RTC 2 +// Bits in powman_hw->last_swcore_pwrup. Bit 0 is a chip reset, bits 1 to 4 are +// the four GPIO wakeups and bit 6 is the timer alarm. +#define RP_POWMAN_PWRUP_GPIO_LSB 1 +#define RP_POWMAN_PWRUP_GPIO_BITS 0x1e +#define RP_POWMAN_PWRUP_ALARM_BITS 0x40 + +#if PICO_RP2350 +// Returns true when this boot is powman powering the core back up for a deep +// sleep alarm. Returns false after any other kind of reset. +bool alarm_woke_from_powman(void); +#endif + typedef union { alarm_pin_pinalarm_obj_t pin_alarm; alarm_time_timealarm_obj_t time_alarm; diff --git a/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c b/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c index 350306390ee..aecdc4b98e1 100644 --- a/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c +++ b/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c @@ -15,6 +15,21 @@ #include "hardware/gpio.h" #include "hardware/structs/iobank0.h" +#if PICO_RP2350 +#include "hardware/powman.h" + +// Deep sleep on RP2350 wakes through powman, which has four GPIO wakeup slots. +// The alarm objects are gone by the time we enter deep sleep, so copy what we need. +#define POWMAN_WAKEUP_SLOTS (count_of(powman_hw->pwrup)) +typedef struct { + uint8_t pin_number; + bool edge; + bool value; +} powman_wakeup_t; +static powman_wakeup_t powman_wakeups[POWMAN_WAKEUP_SLOTS]; +static size_t powman_wakeup_count; +#endif + static bool woke_up; static uint64_t alarm_triggered_pins; // 36 actual pins static uint64_t alarm_reserved_pins; // 36 actual pins @@ -89,6 +104,18 @@ mp_obj_t alarm_pin_pinalarm_record_wake_alarm(void) { alarm->base.type = &alarm_pin_pinalarm_type; // TODO: how to obtain the correct pin from memory? alarm->pin = NULL; + #if PICO_RP2350 + // After a powman wake, the wakeup slot that fired still holds its pin number. + if (!woke_up && alarm_woke_from_powman()) { + uint32_t pwrup = powman_hw->last_swcore_pwrup & RP_POWMAN_PWRUP_GPIO_BITS; + for (size_t i = 0; i < POWMAN_WAKEUP_SLOTS; i++) { + if (pwrup & (1u << (i + RP_POWMAN_PWRUP_GPIO_LSB))) { + alarm->pin = mcu_get_pin_by_number(powman_hw->pwrup[i] & POWMAN_PWRUP0_SOURCE_BITS); + break; + } + } + } + #endif return alarm; } @@ -109,13 +136,35 @@ void alarm_pin_pinalarm_reset(void) { } } alarm_reserved_pins = 0; + + #if PICO_RP2350 + for (size_t i = 0; i < POWMAN_WAKEUP_SLOTS; i++) { + powman_disable_gpio_wakeup(i); + } + powman_wakeup_count = 0; + #endif } void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) { + #if PICO_RP2350 + powman_wakeup_count = 0; + #endif for (size_t i = 0; i < n_alarms; i++) { if (mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) { alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]); + #if PICO_RP2350 + if (deep_sleep) { + if (powman_wakeup_count >= POWMAN_WAKEUP_SLOTS) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Too many %q"), MP_QSTR_PinAlarm); + } + powman_wakeups[powman_wakeup_count].pin_number = alarm->pin->number; + powman_wakeups[powman_wakeup_count].edge = alarm->edge; + powman_wakeups[powman_wakeup_count].value = alarm->value; + powman_wakeup_count++; + } + #endif + gpio_init(alarm->pin->number); if (alarm->pull) { // If value is high, the pullup should be off, and vice versa @@ -149,3 +198,24 @@ void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_ob } } } + +#if PICO_RP2350 +// Arm the powman GPIO wakeups recorded by alarm_pin_pinalarm_set_alarms(). +// Call this just before powering down for deep sleep. +void alarm_pin_pinalarm_enable_powman_wakeups(void) { + for (size_t i = 0; i < powman_wakeup_count; i++) { + powman_enable_gpio_wakeup(i, powman_wakeups[i].pin_number, + powman_wakeups[i].edge, powman_wakeups[i].value); + } +} + +bool alarm_pin_pinalarm_first_powman_wakeup(uint *pin_number, bool *edge, bool *value) { + if (powman_wakeup_count == 0) { + return false; + } + *pin_number = powman_wakeups[0].pin_number; + *edge = powman_wakeups[0].edge; + *value = powman_wakeups[0].value; + return true; +} +#endif diff --git a/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.h b/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.h index b9f81da847f..92069638bd5 100644 --- a/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.h +++ b/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.h @@ -27,3 +27,11 @@ void alarm_pin_pinalarm_light_reset(void); void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms); bool alarm_pin_pinalarm_woke_this_cycle(void); void alarm_pin_pinalarm_entering_deep_sleep(void); +#if PICO_RP2350 +// Arms the powman GPIO wakeups for the pin alarms given to +// alarm_pin_pinalarm_set_alarms() with deep_sleep true. +void alarm_pin_pinalarm_enable_powman_wakeups(void); +// Returns false and leaves the outputs alone when no pin alarm was set for deep sleep. +// Otherwise fills in the pin alarm that uses powman wakeup slot 0 and returns true. +bool alarm_pin_pinalarm_first_powman_wakeup(uint *pin_number, bool *edge, bool *value); +#endif diff --git a/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.c b/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.c index 1f7c3f9edab..c007116a571 100644 --- a/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.c +++ b/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.c @@ -10,13 +10,14 @@ #include "shared-bindings/alarm/time/TimeAlarm.h" #include "shared-bindings/time/__init__.h" -#include "shared/timeutils/timeutils.h" - #include "hardware/gpio.h" -#include "hardware/rtc.h" +#include "pico/aon_timer.h" static bool woke_up = false; static bool _timealarm_set = false; +#if PICO_RP2350 +static uint64_t _wakeup_ms; +#endif static void timer_callback(void) { woke_up = true; @@ -53,7 +54,7 @@ bool alarm_time_timealarm_woke_this_cycle(void) { } void alarm_time_timealarm_reset(void) { - rtc_disable_alarm(); + aon_timer_disable_alarm(); woke_up = false; } @@ -81,28 +82,19 @@ void alarm_time_timealarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_ // Compute how long to actually sleep, considering the time now. mp_float_t mono_seconds_to_date = uint64_to_float(common_hal_time_monotonic_ms()) / 1000.0f; mp_float_t wakeup_in_secs = MAX(0.0f, timealarm->monotonic_time - mono_seconds_to_date); - datetime_t t; - - rtc_get_datetime(&t); + struct timespec t; - uint32_t rtc_seconds_to_date = timeutils_seconds_since_2000(t.year, t.month, - t.day, t.hour, t.min, t.sec); + aon_timer_get_time(&t); // The float value is always slightly under, so add 1 to compensate - uint32_t alarm_seconds = rtc_seconds_to_date + (uint32_t)wakeup_in_secs + 1; - timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(alarm_seconds, &tm); - - // reuse t - t.hour = tm.tm_hour; - t.min = tm.tm_min; - t.sec = tm.tm_sec; - t.day = tm.tm_mday; - t.month = tm.tm_mon; - t.year = tm.tm_year; - t.dotw = (tm.tm_wday + 1) % 7; + t.tv_sec += (uint32_t)wakeup_in_secs + 1; + t.tv_nsec = 0; - rtc_set_alarm(&t, &timer_callback); + // On RP2350 the last argument makes the alarm a powman wakeup source. + aon_timer_enable_alarm(&t, &timer_callback, deep_sleep); + #if PICO_RP2350 + _wakeup_ms = (uint64_t)t.tv_sec * 1000; + #endif woke_up = false; } @@ -110,3 +102,9 @@ void alarm_time_timealarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_ bool alarm_time_timealarm_is_set(void) { return _timealarm_set; } + +#if PICO_RP2350 +uint64_t alarm_time_timealarm_get_wakeup_ms(void) { + return _wakeup_ms; +} +#endif diff --git a/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.h b/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.h index 824bf5ef9e0..83db9bbe591 100644 --- a/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.h +++ b/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.h @@ -20,3 +20,8 @@ void alarm_time_timealarm_reset(void); void alarm_time_timealarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms); bool alarm_time_timealarm_woke_this_cycle(void); bool alarm_time_timealarm_is_set(void); +#if PICO_RP2350 +// Returns the AON timer time, in milliseconds, of the last alarm given to +// alarm_time_timealarm_set_alarms(). Only meaningful when alarm_time_timealarm_is_set(). +uint64_t alarm_time_timealarm_get_wakeup_ms(void); +#endif diff --git a/ports/raspberrypi/common-hal/busio/SPI.c b/ports/raspberrypi/common-hal/busio/SPI.c index aeb06d919ae..f4d86f538d7 100644 --- a/ports/raspberrypi/common-hal/busio/SPI.c +++ b/ports/raspberrypi/common-hal/busio/SPI.c @@ -65,6 +65,8 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, mp_raise_ValueError(MP_ERROR_TEXT("SPI peripheral in use")); } + self->async_active = false; + self->dma_kept = false; self->target_frequency = 250000; self->real_frequency = spi_init(self->peripheral, self->target_frequency); @@ -103,6 +105,12 @@ void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { if (common_hal_busio_spi_deinited(self)) { return; } + common_hal_busio_spi_end(self); + if (self->dma_kept) { + dma_channel_unclaim(self->dma_tx); + dma_channel_unclaim(self->dma_rx); + self->dma_kept = false; + } spi_deinit(self->peripheral); common_hal_reset_pin(self->clock); @@ -161,90 +169,110 @@ void common_hal_busio_spi_unlock(busio_spi_obj_t *self) { self->has_lock = false; } -static bool _transfer(busio_spi_obj_t *self, +// Start a transfer. With DMA it runs in the background and _end() finishes it; otherwise it is +// done in software before this returns. An out or in buffer shorter than the transfer is one +// byte repeated or dropped. keep_dma keeps the DMA channels until deinit, for buses that send +// often. Returns whether DMA is running. +static bool _start(busio_spi_obj_t *self, const uint8_t *data_out, size_t out_len, - uint8_t *data_in, size_t in_len) { - // Use DMA for large transfers if channels are available - const size_t dma_min_size_threshold = 32; - int chan_tx = -1; - int chan_rx = -1; + uint8_t *data_in, size_t in_len, bool keep_dma) { size_t len = MAX(out_len, in_len); - if (len >= dma_min_size_threshold) { - // Use two DMA channels to service the two FIFOs - chan_tx = dma_claim_unused_channel(false); - chan_rx = dma_claim_unused_channel(false); - } - bool has_dma_channels = chan_rx >= 0 && chan_tx >= 0; // Only use DMA if both data buffers are in SRAM. Otherwise, we'll stall the DMA with PSRAM or flash cache misses. - bool data_in_sram = data_in >= (uint8_t *)SRAM_BASE && data_out >= (uint8_t *)SRAM_BASE; - bool use_dma = has_dma_channels && data_in_sram; + bool use_dma = len >= 32 && data_in >= (uint8_t *)SRAM_BASE && data_out >= (uint8_t *)SRAM_BASE; + if (use_dma && !self->dma_kept) { + int chan_tx = dma_claim_unused_channel(false); + int chan_rx = dma_claim_unused_channel(false); + if (chan_tx >= 0 && chan_rx >= 0) { + self->dma_tx = chan_tx; + self->dma_rx = chan_rx; + self->dma_kept = keep_dma; + } else { + // If we have claimed only one channel successfully, release it. + if (chan_tx >= 0) { + dma_channel_unclaim(chan_tx); + } + if (chan_rx >= 0) { + dma_channel_unclaim(chan_rx); + } + use_dma = false; + } + } if (use_dma) { - dma_channel_config c = dma_channel_get_default_config(chan_tx); + dma_channel_config c = dma_channel_get_default_config(self->dma_tx); channel_config_set_transfer_data_size(&c, DMA_SIZE_8); channel_config_set_dreq(&c, spi_get_index(self->peripheral) ? DREQ_SPI1_TX : DREQ_SPI0_TX); channel_config_set_read_increment(&c, out_len == len); channel_config_set_write_increment(&c, false); - dma_channel_configure(chan_tx, &c, + dma_channel_configure(self->dma_tx, &c, &spi_get_hw(self->peripheral)->dr, data_out, len, false); - c = dma_channel_get_default_config(chan_rx); + c = dma_channel_get_default_config(self->dma_rx); channel_config_set_transfer_data_size(&c, DMA_SIZE_8); channel_config_set_dreq(&c, spi_get_index(self->peripheral) ? DREQ_SPI1_RX : DREQ_SPI0_RX); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, in_len == len); - dma_channel_configure(chan_rx, &c, + dma_channel_configure(self->dma_rx, &c, data_in, &spi_get_hw(self->peripheral)->dr, len, false); - dma_start_channel_mask((1u << chan_rx) | (1u << chan_tx)); - while (dma_channel_is_busy(chan_rx) || dma_channel_is_busy(chan_tx)) { - // TODO: We should idle here until we get a DMA interrupt or something else. - RUN_BACKGROUND_TASKS; - } - } - - // If we have claimed only one channel successfully, we should release immediately. This also - // releases the DMA after use_dma has been done. - if (chan_rx >= 0) { - dma_channel_unclaim(chan_rx); - } - if (chan_tx >= 0) { - dma_channel_unclaim(chan_tx); + dma_start_channel_mask((1u << self->dma_rx) | (1u << self->dma_tx)); + return true; } - if (!use_dma) { - // Use software for small transfers, or if couldn't claim two DMA channels - // Never have more transfers in flight than will fit into the RX FIFO, - // else FIFO will overflow if this code is heavily interrupted. - const size_t fifo_depth = 8; - size_t rx_remaining = len; - size_t tx_remaining = len; - - while (rx_remaining || tx_remaining) { - if (tx_remaining && spi_is_writable(self->peripheral) && rx_remaining - tx_remaining < fifo_depth) { - spi_get_hw(self->peripheral)->dr = (uint32_t)*data_out; - // Increment only if the buffer is the transfer length. It's 1 otherwise. - if (out_len == len) { - data_out++; - } - --tx_remaining; + // Use software for small transfers, or if couldn't claim two DMA channels + // Never have more transfers in flight than will fit into the RX FIFO, + // else FIFO will overflow if this code is heavily interrupted. + const size_t fifo_depth = 8; + size_t rx_remaining = len; + size_t tx_remaining = len; + + while (rx_remaining || tx_remaining) { + if (tx_remaining && spi_is_writable(self->peripheral) && rx_remaining - tx_remaining < fifo_depth) { + spi_get_hw(self->peripheral)->dr = (uint32_t)*data_out; + // Increment only if the buffer is the transfer length. It's 1 otherwise. + if (out_len == len) { + data_out++; } - if (rx_remaining && spi_is_readable(self->peripheral)) { - *data_in = (uint8_t)spi_get_hw(self->peripheral)->dr; - // Increment only if the buffer is the transfer length. It's 1 otherwise. - if (in_len == len) { - data_in++; - } - --rx_remaining; + --tx_remaining; + } + if (rx_remaining && spi_is_readable(self->peripheral)) { + *data_in = (uint8_t)spi_get_hw(self->peripheral)->dr; + // Increment only if the buffer is the transfer length. It's 1 otherwise. + if (in_len == len) { + data_in++; } + --rx_remaining; + } + RUN_BACKGROUND_TASKS; + } + return false; +} + +// Wait for a DMA transfer started by _start(). The RX channel finishes last. +static void _end(busio_spi_obj_t *self, bool background_tasks) { + while (dma_channel_is_busy(self->dma_rx)) { + if (background_tasks) { RUN_BACKGROUND_TASKS; } } + if (!self->dma_kept) { + dma_channel_unclaim(self->dma_tx); + dma_channel_unclaim(self->dma_rx); + } +} + +static bool _transfer(busio_spi_obj_t *self, + const uint8_t *data_out, size_t out_len, + uint8_t *data_in, size_t in_len) { + if (_start(self, data_out, out_len, data_in, in_len, false)) { + // TODO: We should idle here until we get a DMA interrupt or something else. + _end(self, true); + } return true; } @@ -254,6 +282,26 @@ bool common_hal_busio_spi_write(busio_spi_obj_t *self, return _transfer(self, data, len, (uint8_t *)&data_in, MIN(len, 4)); } +void common_hal_busio_spi_write_start(busio_spi_obj_t *self, const uint8_t *data, size_t len, + circuitpy_async_flag_t *done) { + common_hal_busio_spi_end(self); + CIRCUITPY_ASYNC_FLAG_INIT(done); + self->async_done = done; + self->async_active = _start(self, data, len, &self->discard, 1, true); + if (!self->async_active) { + CIRCUITPY_ASYNC_FLAG_SET(done); + } +} + +void common_hal_busio_spi_end(busio_spi_obj_t *self) { + if (self->async_active) { + // No background tasks here: the caller holds the bus, and one of them may want it. + _end(self, false); + self->async_active = false; + CIRCUITPY_ASYNC_FLAG_SET(self->async_done); + } +} + bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size_t len, uint8_t write_value) { uint32_t data_out = write_value << 24 | write_value << 16 | write_value << 8 | write_value; diff --git a/ports/raspberrypi/common-hal/busio/SPI.h b/ports/raspberrypi/common-hal/busio/SPI.h index 3d43c1eff00..4acb15b7b2c 100644 --- a/ports/raspberrypi/common-hal/busio/SPI.h +++ b/ports/raspberrypi/common-hal/busio/SPI.h @@ -9,6 +9,7 @@ #include "common-hal/microcontroller/Pin.h" #include "py/obj.h" +#include "supervisor/shared/async_flag.h" #include "hardware/spi.h" @@ -24,4 +25,10 @@ typedef struct { uint8_t polarity; uint8_t phase; uint8_t bits; + bool async_active; // a write_start DMA transfer may still be running + bool dma_kept; // dma_tx and dma_rx are ours until deinit + uint8_t dma_tx; + uint8_t dma_rx; + uint8_t discard; // RX target of write_start + circuitpy_async_flag_t *async_done; } busio_spi_obj_t; diff --git a/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.c b/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.c index d0a7a1d7b08..114617a4d86 100644 --- a/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.c +++ b/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.c @@ -48,30 +48,15 @@ /* .wrap */ \ } -static mcu_pin_obj_t *pin_from_number(uint8_t number) { - const mp_map_t *mcu_map = &mcu_pin_globals.map; - for (uint8_t i = 0; i < mcu_map->alloc; i++) { - mp_obj_t val = mcu_map->table[i].value; - if (!mp_obj_is_type(val, &mcu_pin_type)) { - continue; - } - mcu_pin_obj_t *pin = MP_OBJ_TO_PTR(val); - if (pin->number == number) { - return pin; - } - } - return NULL; -} - void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_parallelimagecapture_obj_t *self, - const uint8_t data_pins[], + const mcu_pin_obj_t **data_pins, uint8_t data_count, const mcu_pin_obj_t *data_clock, const mcu_pin_obj_t *vertical_sync, const mcu_pin_obj_t *horizontal_reference) { for (int i = 1; i < data_count; i++) { - if (data_pins[i] - data_pins[0] != i) { + if (common_hal_mcu_pin_number(data_pins[i]) - common_hal_mcu_pin_number(data_pins[0]) != i) { mp_raise_RuntimeError(MP_ERROR_TEXT("Pins must be sequential")); } } @@ -84,7 +69,7 @@ void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_paralle 0, 0, // init NULL, 0, // may_exec NULL, 0, PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // out pins - pin_from_number(data_pins[0]), data_count, // in pins + data_pins[0], data_count, // in pins PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // in pulls NULL, 0, PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // set pins #if DEBUG_STATE_MACHINE diff --git a/ports/raspberrypi/common-hal/microcontroller/Pin.c b/ports/raspberrypi/common-hal/microcontroller/Pin.c index 3c5286d36c4..e58b2fa3879 100644 --- a/ports/raspberrypi/common-hal/microcontroller/Pin.c +++ b/ports/raspberrypi/common-hal/microcontroller/Pin.c @@ -127,10 +127,6 @@ uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin) { return pin->number; } -void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { - return claim_pin(pin); -} - void common_hal_mcu_pin_reset_number(uint8_t pin_no) { reset_pin_number(pin_no); } diff --git a/ports/raspberrypi/common-hal/microcontroller/Pin.h b/ports/raspberrypi/common-hal/microcontroller/Pin.h index ce6e33a24a8..ca88c1d1812 100644 --- a/ports/raspberrypi/common-hal/microcontroller/Pin.h +++ b/ports/raspberrypi/common-hal/microcontroller/Pin.h @@ -20,6 +20,7 @@ bool board_reset_pin_number(uint8_t pin_number); void reset_all_pins(void); +void common_hal_reset_pin(const mcu_pin_obj_t *pin); // reset_pin_number takes the pin number instead of the pointer so that objects don't // need to store a full pointer. void reset_pin_number(uint8_t pin_number); @@ -27,6 +28,8 @@ void never_reset_pin_number(uint8_t pin_number); void claim_pin(const mcu_pin_obj_t *pin); bool pin_number_is_free(uint8_t pin_number); +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); + #if CIRCUITPY_CYW43 extern bool cyw_ever_init; void reset_pin_number_cyw(uint8_t pin_number); diff --git a/ports/raspberrypi/common-hal/microcontroller/Processor.c b/ports/raspberrypi/common-hal/microcontroller/Processor.c index fbbef51acb4..3e262d7ef7a 100644 --- a/ports/raspberrypi/common-hal/microcontroller/Processor.c +++ b/ports/raspberrypi/common-hal/microcontroller/Processor.c @@ -14,6 +14,9 @@ #include "shared-bindings/microcontroller/Processor.h" #include "shared-bindings/microcontroller/ResetReason.h" #include "shared-bindings/time/__init__.h" +#if CIRCUITPY_ALARM +#include "common-hal/alarm/__init__.h" +#endif #include "pico/stdlib.h" #include "hardware/adc.h" @@ -150,6 +153,13 @@ mcu_reset_reason_t common_hal_mcu_processor_get_reset_reason(void) { if (chip_reset_reg & POWMAN_CHIP_RESET_HAD_POR_BITS) { reason = MCU_RESET_REASON_POWER_ON; } + + #if CIRCUITPY_ALARM + // The chip reset flags above stay set through a powman wake, so check it last. + if (alarm_woke_from_powman()) { + reason = MCU_RESET_REASON_DEEP_SLEEP_ALARM; + } + #endif #endif // Check watchdog after chip reset since watchdog doesn't clear chip_reset, while chip_reset clears the watchdog diff --git a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c index 2f815cd2ee0..612b7b5ec2e 100644 --- a/ports/raspberrypi/common-hal/rp2pio/StateMachine.c +++ b/ports/raspberrypi/common-hal/rp2pio/StateMachine.c @@ -433,7 +433,9 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, added = true; } else { uint program_offset; - bool r = pio_claim_free_sm_and_add_program_for_gpio_range(&program_struct, &pio, (uint *)&state_machine, &program_offset, gpio_base, gpio_count, true); + bool r = gpio_count + ? pio_claim_free_sm_and_add_program_for_gpio_range(&program_struct, &pio, (uint *)&state_machine, &program_offset, gpio_base, gpio_count, true) + : pio_claim_free_sm_and_add_program(&program_struct, &pio, (uint *)&state_machine, &program_offset); if (!r) { return false; } diff --git a/ports/raspberrypi/common-hal/rtc/pico_time.c b/ports/raspberrypi/common-hal/rtc/pico_time.c new file mode 100644 index 00000000000..c2e3ce9e974 --- /dev/null +++ b/ports/raspberrypi/common-hal/rtc/pico_time.c @@ -0,0 +1,39 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Vladimir Smitka +// +// SPDX-License-Identifier: MIT + +// The SDK expects the port to provide pico_localtime_r() and pico_mktime(); its +// weak defaults call newlib's localtime_r() and mktime(). CircuitPython has the +// same calendar arithmetic in shared/timeutils, so use that and keep newlib's +// time functions, and what they link in, out of the image. +// +// Newlib would run without a TZ setting here, so both compute UTC. + +#include + +#include "pico/util/datetime.h" +#include "shared/timeutils/timeutils.h" + +struct tm *pico_localtime_r(const time_t *time, struct tm *tm) { + timeutils_struct_time_t t; + timeutils_seconds_since_1970_to_struct_time((timeutils_timestamp_t)*time, &t); + tm->tm_year = t.tm_year - 1900; + tm->tm_mon = t.tm_mon - 1; + tm->tm_mday = t.tm_mday; + tm->tm_hour = t.tm_hour; + tm->tm_min = t.tm_min; + tm->tm_sec = t.tm_sec; + tm->tm_wday = (t.tm_wday + 1) % 7; // timeutils counts from Monday, struct tm from Sunday + tm->tm_yday = t.tm_yday - 1; + tm->tm_isdst = 0; + return tm; +} + +time_t pico_mktime(struct tm *tm) { + time_t t = (time_t)timeutils_mktime_1970(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + pico_localtime_r(&t, tm); // mktime() also normalises the fields it was given + return t; +} diff --git a/ports/raspberrypi/common-hal/sdioio/SDCard.c b/ports/raspberrypi/common-hal/sdioio/SDCard.c index 04b78dbc4d2..936a5461ae7 100644 --- a/ports/raspberrypi/common-hal/sdioio/SDCard.c +++ b/ports/raspberrypi/common-hal/sdioio/SDCard.c @@ -133,8 +133,9 @@ static void check_whole_block(mp_buffer_info_t *bufinfo) { } } -// Native function for the VFS blockdev layer. The PIO driver is synchronous and -// polling, so these block until the transfer completes. +// Native function for the VFS blockdev layer. The PIO driver is polling, so +// these block until the transfer completes. Writes leave the multi-block +// transfer open until the next sync. mp_negative_errno_t sdioio_sdcard_readblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t num_blocks) { sdioio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -178,8 +179,9 @@ bool sdioio_sdcard_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, switch (cmd) { case MP_BLOCKDEV_IOCTL_DEINIT: case MP_BLOCKDEV_IOCTL_SYNC: - // SDIO operations are synchronous, no action needed. - return true; + // The driver keeps multi-block writes open between calls, so end + // the transfer and wait for the card to finish programming. + return sdfat_pio_card_sync(&self->card); case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: *out_value = common_hal_sdioio_sdcard_get_count(self); @@ -215,6 +217,8 @@ void common_hal_sdioio_sdcard_deinit(sdioio_sdcard_obj_t *self) { unregister_card(self); + // Commit any open write before the PIO is released. + sdfat_pio_card_sync(&self->card); sdfat_pio_card_end(&self->card); sdfat_pio_card_free(&self->card); diff --git a/ports/raspberrypi/common-hal/sdioio/sdfat_pio/shim.cpp b/ports/raspberrypi/common-hal/sdioio/sdfat_pio/shim.cpp index 5aeea4c8e94..b4342d7bedd 100644 --- a/ports/raspberrypi/common-hal/sdioio/sdfat_pio/shim.cpp +++ b/ports/raspberrypi/common-hal/sdioio/sdfat_pio/shim.cpp @@ -65,6 +65,10 @@ bool sdfat_pio_card_write_sectors(void *storage, uint32_t start_sector, return reinterpret_cast(storage)->writeSectors(start_sector, src, num_sectors); } +bool sdfat_pio_card_sync(void *storage) { + return reinterpret_cast(storage)->syncDevice(); +} + uint8_t sdfat_pio_card_error_code(void *storage) { return reinterpret_cast(storage)->errorCode(); } diff --git a/ports/raspberrypi/common-hal/sdioio/sdfat_pio/shim.h b/ports/raspberrypi/common-hal/sdioio/sdfat_pio/shim.h index c968ea0f734..b749b6de948 100644 --- a/ports/raspberrypi/common-hal/sdioio/sdfat_pio/shim.h +++ b/ports/raspberrypi/common-hal/sdioio/sdfat_pio/shim.h @@ -59,6 +59,10 @@ bool sdfat_pio_card_read_sectors(void *storage, uint32_t start_sector, bool sdfat_pio_card_write_sectors(void *storage, uint32_t start_sector, const uint8_t *src, size_t num_sectors); +// Ends any open multi-block transfer (CMD12) and waits for the card to finish +// programming. Returns false on timeout. +bool sdfat_pio_card_sync(void *storage); + // Last error code from the driver (see SdCardInfo.h). uint8_t sdfat_pio_card_error_code(void *storage); diff --git a/ports/raspberrypi/common-hal/socketpool/Socket.c b/ports/raspberrypi/common-hal/socketpool/Socket.c index 840558d1ac3..357c1162f12 100644 --- a/ports/raspberrypi/common-hal/socketpool/Socket.c +++ b/ports/raspberrypi/common-hal/socketpool/Socket.c @@ -67,8 +67,6 @@ static mp_obj_t socketpool_ip_addr_and_port_to_tuple(const ip_addr_t *addr, int return mp_obj_new_tuple(n, args); } -#define MICROPY_PY_LWIP_SOCK_RAW (1) - #if 0 // print debugging info #define DEBUG_printf DEBUG_printf #else // don't print debugging info diff --git a/ports/raspberrypi/common-hal/usb_host/Port.c b/ports/raspberrypi/common-hal/usb_host/Port.c index f06e9e5d71d..22360320434 100644 --- a/ports/raspberrypi/common-hal/usb_host/Port.c +++ b/ports/raspberrypi/common-hal/usb_host/Port.c @@ -8,7 +8,9 @@ #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/microcontroller/Processor.h" #include "shared-bindings/usb_host/Port.h" +#include "supervisor/port_heap.h" #include "supervisor/shared/serial.h" +#include "supervisor/shared/settings.h" #include "supervisor/usb.h" #include "pico/time.h" @@ -174,6 +176,32 @@ usb_host_port_obj_t *common_hal_usb_host_port_construct(const mcu_pin_obj_t *dp, common_hal_never_reset_pin(dp); common_hal_never_reset_pin(dm); + #if PIO_USB_HOST_ISOCHRONOUS + // Ring storage for isochronous IN endpoints (UVC cameras, UAC audio), + // opt-in via settings.toml. Set before core 1 launches so it never + // observes a half-set ring. + mp_int_t iso_size = 0; + if (settings_get_int("CIRCUITPY_USB_HOST_ISO_BUFFER_SIZE", &iso_size) == SETTINGS_OK + && iso_size > 0) { + // Round up to a power of two and clamp to [2048, 65536] + uint32_t rounded = 2048; + while (rounded < (uint32_t)iso_size && rounded < 65536) { + rounded <<= 1; + } + // dma_capable=true forces the internal-SRAM heap. Never PSRAM: PSRAM + // is inside the XIP region that core 1's MPU blocks below, and the + // ring is written from core 1. Like the port itself, the buffer is + // never freed; it survives soft reload. + uint8_t *iso_buf = port_malloc(rounded, true); + if (iso_buf != NULL) { + pio_usb_host_set_iso_ring(iso_buf, rounded); + } else { + mp_raise_RuntimeError_varg(MP_ERROR_TEXT("Failed to allocate %q buffer"), + MP_QSTR_USB_HOST_ISO_BUFFER); + } + } + #endif + // Core 1 will run the SOF interrupt directly. _core1_ready = false; multicore_launch_core1(core1_main); diff --git a/ports/raspberrypi/common-hal/wifi/Radio.c b/ports/raspberrypi/common-hal/wifi/Radio.c index 73e7794ecc2..f7f382cae79 100644 --- a/ports/raspberrypi/common-hal/wifi/Radio.c +++ b/ports/raspberrypi/common-hal/wifi/Radio.c @@ -196,6 +196,9 @@ void common_hal_wifi_radio_start_ap(wifi_radio_obj_t *self, uint8_t *ssid, size_ mp_raise_RuntimeError(MP_ERROR_TEXT("WiFi is not enabled")); } + // The CYW43 is 2.4 GHz only. + mp_arg_validate_int_max(channel, 13, MP_QSTR_channel); + /* TODO: If the AP is stopped once it cannot be restarted. * This means that if if the user does: * diff --git a/ports/raspberrypi/lib/Pico-PIO-USB b/ports/raspberrypi/lib/Pico-PIO-USB index a5a2f5ae919..9e502a55998 160000 --- a/ports/raspberrypi/lib/Pico-PIO-USB +++ b/ports/raspberrypi/lib/Pico-PIO-USB @@ -1 +1 @@ -Subproject commit a5a2f5ae91988449ba576ec9d237e806d5cd4416 +Subproject commit 9e502a559983a6b857b8c6328c84729cbe809749 diff --git a/ports/raspberrypi/link-rp2350.ld b/ports/raspberrypi/link-rp2350.ld index 3c339b03fe2..4338b4afc02 100644 --- a/ports/raspberrypi/link-rp2350.ld +++ b/ports/raspberrypi/link-rp2350.ld @@ -205,6 +205,16 @@ SECTIONS _ld_dtcm_bss_start = ADDR(.dtcm_bss); _ld_dtcm_bss_size = SIZEOF(.dtcm_bss); + /* Kept powered through deep sleep by pico_low_power. Holds alarm.sleep_memory. */ + .persistent_data (NOLOAD) : { + . = ALIGN(4); + __persistent_data_start__ = .; + *(.uninitialized) + *(.persistent_data*) + . = ALIGN(4); + __persistent_data_end__ = .; + } > RAM + .uninitialized_data (COPY): { . = ALIGN(4); *(.uninitialized_data*) diff --git a/ports/raspberrypi/mpconfigport.h b/ports/raspberrypi/mpconfigport.h index 6abf9ea0547..7d47658e729 100644 --- a/ports/raspberrypi/mpconfigport.h +++ b/ports/raspberrypi/mpconfigport.h @@ -8,6 +8,10 @@ #include "hardware/platform_defs.h" +// CIRCUITPY-CHANGE: CircuitPython supports raw sockets through its own lwip +// integration, independently of MICROPY_PY_LWIP. +#define MICROPY_PY_LWIP_SOCK_RAW (1) + #if PICO_RP2040 #define MICROPY_PY_SYS_PLATFORM "RP2040" #endif diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index 551ab00ab47..f5358b5a12e 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -6,6 +6,9 @@ CIRCUITPY_OPTIMIZE_PROPERTY_FLASH_SIZE ?= 1 CIRCUITPY_WIFI_RADIO_SETTABLE_MAC_ADDRESS = 0 CIRCUITPY_RP2PIO ?= 1 +CIRCUITPY_BUSIO_SPI_ASYNC ?= 1 +# The 24 KB stack has room for a larger display refresh buffer. +CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE ?= 2048 CIRCUITPY_NEOPIXEL_WRITE ?= $(CIRCUITPY_RP2PIO) CIRCUITPY_FLOPPYIO ?= 1 CIRCUITPY_FRAMEBUFFERIO ?= $(CIRCUITPY_DISPLAYIO) @@ -53,9 +56,9 @@ CIRCUITPY_AUDIOPWMIO ?= 1 CIRCUITPY_AUDIOMIXER ?= 1 -ifeq ($(CHIP_VARIANT),RP2040) CIRCUITPY_ALARM ?= 1 +ifeq ($(CHIP_VARIANT),RP2040) # Default PICODVI off because it uses RAM to store code run on the second CPU for RP2040. CIRCUITPY_PICODVI ?= 0 @@ -69,8 +72,6 @@ ifeq ($(CHIP_VARIANT),RP2350) # RP2350 has PSRAM that is not DMA-capable CIRCUITPY_ALL_MEMORY_DMA_CAPABLE = 0 -# This needs to be implemented. -CIRCUITPY_ALARM = 0 # Default PICODVI on because it doesn't require much code in RAM to talk to HSTX. CIRCUITPY_PICODVI ?= 1 diff --git a/ports/raspberrypi/sdk b/ports/raspberrypi/sdk index a1438dff1d3..079c6f39023 160000 --- a/ports/raspberrypi/sdk +++ b/ports/raspberrypi/sdk @@ -1 +1 @@ -Subproject commit a1438dff1d38bd9c65dbd693f0e5db4b9ae91779 +Subproject commit 079c6f39023649b154152db30f1d781e884879bc diff --git a/ports/renode/Makefile b/ports/renode/Makefile deleted file mode 100644 index b69d8d251ef..00000000000 --- a/ports/renode/Makefile +++ /dev/null @@ -1,98 +0,0 @@ -# This file is part of the CircuitPython project: https://circuitpython.org -# -# SPDX-FileCopyrightText: Copyright (c) 2024 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -include ../../py/circuitpy_mkenv.mk - -CROSS_COMPILE = arm-none-eabi- - -INC += \ - -I. \ - -I../.. \ - -I../lib/mp-readline \ - -I../shared/timeutils \ - -Iboards/$(BOARD) \ - -Iboards/ \ - -isystem ./../../lib/cmsis/inc \ - -I$(BUILD) - -CFLAGS += -ggdb3 -Os - -DISABLE_WARNINGS = -Wno-cast-align -CFLAGS += $(INC) -Wall -Werror -std=gnu11 -fshort-enums $(BASE_CFLAGS) $(CFLAGS_MOD) $(COPT) $(DISABLE_WARNINGS) -Werror=missing-prototypes -Werror=old-style-definition - -CFLAGS += \ - -march=armv6-m \ - -mthumb \ - -mabi=aapcs \ - -mcpu=cortex-m0plus \ - -msoft-float \ - -mfloat-abi=soft \ - --specs=nano.specs - -# Use toolchain libm if we're not using our own. -ifndef INTERNAL_LIBM -LIBS += -lm -endif - -LIBS += -lc - -SRC_C += \ - boards/$(BOARD)/board.c \ - boards/$(BOARD)/pins.c \ - background.c \ - mphalport.c \ - -SRC_S = shared/runtime/gchelper_thumb1.s - -SRC_C += shared/runtime/gchelper_native.c - -OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) -ifeq ($(INTERNAL_LIBM),1) -OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) -endif -OBJ += $(addprefix $(BUILD)/, $(SRC_CIRCUITPY_COMMON:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) - -$(BUILD)/%.o: $(BUILD)/%.S - $(STEPECHO) "CC $<" - $(Q)$(CC) $(CFLAGS) -c -o $@ $< - -SRC_QSTR += $(SRC_C) $(SRC_SUPERVISOR) $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED) $(SRC_CIRCUITPY_COMMON) - -all: $(BUILD)/firmware.elf $(BUILD)/circuitpy.img - -BOARD_LD := $(wildcard boards/$(BOARD)/link.ld) - -ifneq ($(BOARD_LD),) - LINKER_SCRIPTS = -Wl,-T,$(BOARD_LD) -endif - -LINKER_SCRIPTS += -Wl,-T,link.ld - -$(BUILD)/circuitpy.img: circuitpy/code.py - $(STEPECHO) "Create $@" - $(Q)dd if=/dev/zero of=$(BUILD)/circuitpy.img bs=1 count=0 seek=512K - $(Q)mkfs.fat -n CIRCUITPY --offset=0 $(BUILD)/circuitpy.img - $(Q)mcopy -i $(BUILD)/circuitpy.img circuitpy/* :: - -ifeq ($(VALID_BOARD),) -$(BUILD)/firmware.elf: invalid-board -else -$(BUILD)/firmware.elf: $(OBJ) $(BOARD_LD) link.ld - $(STEPECHO) "LINK $@" - $(Q)echo $(OBJ) > $(BUILD)/firmware.objs - $(Q)echo $(PICO_LDFLAGS) > $(BUILD)/firmware.ldflags - $(Q)$(CC) -o $@ $(CFLAGS) @$(BUILD)/firmware.ldflags $(LINKER_SCRIPTS) -Wl,--print-memory-usage -Wl,-Map=$@.map -Wl,-cref -Wl,--gc-sections @$(BUILD)/firmware.objs -Wl,-lc - $(Q)$(SIZE) $@ | $(PYTHON) $(TOP)/tools/build_memory_info.py $@.map $(BUILD) -endif - -$(BUILD)/firmware.bin: $(BUILD)/firmware.elf - $(STEPECHO) "Create $@" - $(Q)$(OBJCOPY) -O binary -R .dtcm_bss $^ $@ - -include $(TOP)/py/mkrules.mk diff --git a/ports/renode/README.md b/ports/renode/README.md deleted file mode 100644 index 16b6ba6ca4c..00000000000 --- a/ports/renode/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Renode - -Renode is an emulator targeting microcontroller-class devices. This port is a -minimal version of CircuitPython that runs under Renode. Renode is designed to -mimic full microcontrollers but CP uses more peripherals than what Renode has -implemented so far. This port allows us to run on a variety of CPUs without -worrying about peripherals. - -## Running - -1. Get Renode: https://renode.io/#downloads -2. `cd ports/renode` -3. `make BOARD=renode_cortex_m0plus` -4. In another tab: `tio /tmp/cp-uart` -5. `renode` -6. In renode: `include @renode.resc` -7. -8. `start` -9. `pause` -10. `quit` - -Step 4 sets up `tio` to talk to CircuitPython via UART <-> PTY bridge. - -## Other stuff - -### Emulator logging -Renode modules have debug logging that can be enabled with `logLevel` with an int -between `-1` for `NOISY` and `3` for errors only. - -### GDB - -Renode can provide a GDB server. It is very useful for precisely controlling the -emulator's execution. - -``` -machine StartGdbServer 3333 true -``` - -### Execution profiling - -In renode do `cpu EnableProfiler CollapsedStack $ORIGIN/profile.folded` before starting -the emulation. You can view it using [Speedscope](https://www.speedscope.app/). CircuitPython calls -a lot of functions and may overwhelm speedscope. You can enable this tracing over a specific -section of CircuitPython execution to limit the capture size. - -[Related Renode Docs](https://renode.readthedocs.io/en/latest/advanced/execution-tracing.html) - -### Execution tracing -If you want to see every instruction run you can do: `cpu CreateExecutionTracing "tracer_name" $ORIGIN/instruction_trace.txt Disassembly`. diff --git a/ports/renode/Simple32kHz.cs b/ports/renode/Simple32kHz.cs deleted file mode 100644 index 9c7bc13696e..00000000000 --- a/ports/renode/Simple32kHz.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2010-2022 Antmicro -// Copyright (c) 2011-2015 Realtime Embedded -// -// This file is licensed under the MIT License. -// Full license text is available in 'licenses/MIT.txt'. -// -// This is modified for CircuitPython to tick at 32kHz like a slow external -// crystal would. -using System; -using Antmicro.Renode.Core; -using Antmicro.Renode.Peripherals.Bus; -using Antmicro.Renode.Time; -using Antmicro.Renode.Logging; -using System.Threading; - -namespace Antmicro.Renode.Peripherals.Timers -{ - public class Simple32kHz : IDoubleWordPeripheral, IKnownSize - { - public long Size { get { return 0x4; } } - - public Simple32kHz(IMachine machine) - { - machine.ClockSource.AddClockEntry(new ClockEntry(1, 32768, OnTick, this, String.Empty)); - } - - public virtual uint ReadDoubleWord(long offset) - { - return (uint)counter; - } - - public virtual void WriteDoubleWord(long offset, uint value) - { - this.LogUnhandledWrite(offset, value); - } - - public virtual void Reset() - { - Interlocked.Exchange(ref counter, 0); - } - - private void OnTick() - { - Interlocked.Increment(ref counter); - } - - private int counter; - } -} diff --git a/ports/renode/background.c b/ports/renode/background.c deleted file mode 100644 index 9493d3cb827..00000000000 --- a/ports/renode/background.c +++ /dev/null @@ -1,19 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/port.h" - -void port_start_background_tick(void) { -} - -void port_finish_background_tick(void) { -} - -void port_background_tick(void) { -} - -void port_background_task(void) { -} diff --git a/ports/renode/boards/peripherals.repl b/ports/renode/boards/peripherals.repl deleted file mode 100644 index 32eddfd1e54..00000000000 --- a/ports/renode/boards/peripherals.repl +++ /dev/null @@ -1,13 +0,0 @@ -flash_firmware: Memory.MappedMemory @ sysbus 0x00000000 - size: 0x80000 // 512k - -flash_filesystem: Memory.MappedMemory @ sysbus 0x10000000 - size: 0x80000 // 512k - -sram: Memory.MappedMemory @ sysbus 0x20000000 - size: 0x20000 // 128k - - -uart: UART.PicoSoC_SimpleUART @ sysbus 0x40000000 - -time: Timers.Simple32kHz @ sysbus 0x40001000 diff --git a/ports/renode/boards/renode_cortex_m0plus/board.repl b/ports/renode/boards/renode_cortex_m0plus/board.repl deleted file mode 100644 index 3917413d541..00000000000 --- a/ports/renode/boards/renode_cortex_m0plus/board.repl +++ /dev/null @@ -1,10 +0,0 @@ -using "../peripherals.repl" - -cpu: CPU.CortexM @ sysbus - cpuType: "cortex-m0" - nvic: nvic - -nvic: IRQControllers.NVIC @ sysbus 0xE000E000 - priorityMask: 0xF0 - systickFrequency: 100000000 - IRQ -> cpu@0 diff --git a/ports/renode/boards/renode_cortex_m0plus/mpconfigboard.h b/ports/renode/boards/renode_cortex_m0plus/mpconfigboard.h deleted file mode 100644 index b09852e5085..00000000000 --- a/ports/renode/boards/renode_cortex_m0plus/mpconfigboard.h +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_HW_BOARD_NAME "Renode Cortex-M0+" -#define MICROPY_HW_MCU_NAME "cortex-m0+" - -#define DEFAULT_UART_BUS_RX (&pin_GPIO1) -#define DEFAULT_UART_BUS_TX (&pin_GPIO0) - -#define CIRCUITPY_CONSOLE_UART_TX DEFAULT_UART_BUS_TX -#define CIRCUITPY_CONSOLE_UART_RX DEFAULT_UART_BUS_RX diff --git a/ports/renode/boards/renode_cortex_m0plus/mpconfigboard.mk b/ports/renode/boards/renode_cortex_m0plus/mpconfigboard.mk deleted file mode 100644 index 96431b9e7ee..00000000000 --- a/ports/renode/boards/renode_cortex_m0plus/mpconfigboard.mk +++ /dev/null @@ -1 +0,0 @@ -CPU = cortex-m0+ diff --git a/ports/renode/boards/renode_cortex_m0plus/pins.c b/ports/renode/boards/renode_cortex_m0plus/pins.c deleted file mode 100644 index 121d7e912bf..00000000000 --- a/ports/renode/boards/renode_cortex_m0plus/pins.c +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/board/__init__.h" - -static const mp_rom_map_elem_t board_module_globals_table[] = { - CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO1) }, - - { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, -}; -MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/renode/circuitpy/code.py b/ports/renode/circuitpy/code.py deleted file mode 100644 index c1d54872a62..00000000000 --- a/ports/renode/circuitpy/code.py +++ /dev/null @@ -1,4 +0,0 @@ -print("hello from renode") - -# This folder gets merged into a FATFS image loaded into renode. Put what you -# want here and run `make BOARD=renode_cortex_m0plus` diff --git a/ports/renode/common-hal/board/__init__.c b/ports/renode/common-hal/board/__init__.c deleted file mode 100644 index 1ff7f0b489f..00000000000 --- a/ports/renode/common-hal/board/__init__.c +++ /dev/null @@ -1,10 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include - -// Pins aren't actually defined here. They are in the board specific directory -// such as boards/arduino_zero/pins.c. diff --git a/ports/renode/common-hal/busio/I2C.c b/ports/renode/common-hal/busio/I2C.c deleted file mode 100644 index 41649f180b3..00000000000 --- a/ports/renode/common-hal/busio/I2C.c +++ /dev/null @@ -1,63 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/busio/I2C.h" - -#include "py/mperrno.h" -#include "py/runtime.h" - -#define NO_PIN 0xff - -void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, - const mcu_pin_obj_t *scl, const mcu_pin_obj_t *sda, uint32_t frequency, uint32_t timeout) { - mp_raise_NotImplementedError(NULL); -} - -bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { - return true; -} - -void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) { - if (common_hal_busio_i2c_deinited(self)) { - return; - } -} - -bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) { - return false; -} - -bool common_hal_busio_i2c_try_lock(busio_i2c_obj_t *self) { - if (common_hal_busio_i2c_deinited(self)) { - return false; - } - return false; -} - -bool common_hal_busio_i2c_has_lock(busio_i2c_obj_t *self) { - return false; -} - -void common_hal_busio_i2c_unlock(busio_i2c_obj_t *self) { -} - -mp_negative_errno_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr, - const uint8_t *data, size_t len) { - return 0; -} - -mp_negative_errno_t common_hal_busio_i2c_read(busio_i2c_obj_t *self, uint16_t addr, - uint8_t *data, size_t len) { - return -MP_EIO; -} - -mp_negative_errno_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint16_t addr, - uint8_t *out_data, size_t out_len, uint8_t *in_data, size_t in_len) { - return -MP_EIO; -} - -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { -} diff --git a/ports/renode/common-hal/busio/I2C.h b/ports/renode/common-hal/busio/I2C.h deleted file mode 100644 index 69048655560..00000000000 --- a/ports/renode/common-hal/busio/I2C.h +++ /dev/null @@ -1,13 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; -} busio_i2c_obj_t; diff --git a/ports/renode/common-hal/busio/SPI.c b/ports/renode/common-hal/busio/SPI.c deleted file mode 100644 index 1f66fc5ec4a..00000000000 --- a/ports/renode/common-hal/busio/SPI.c +++ /dev/null @@ -1,77 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/busio/SPI.h" - -#include "py/runtime.h" - -void common_hal_busio_spi_construct(busio_spi_obj_t *self, - const mcu_pin_obj_t *clock, const mcu_pin_obj_t *mosi, - const mcu_pin_obj_t *miso, bool half_duplex) { - mp_raise_NotImplementedError(NULL); -} - -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { -} - -bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { - return true; -} - -void common_hal_busio_spi_mark_deinit(busio_spi_obj_t *self) { -} - -void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { - if (common_hal_busio_spi_deinited(self)) { - return; - } -} - -bool common_hal_busio_spi_configure(busio_spi_obj_t *self, - uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) { - return true; -} - -bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) { - if (common_hal_busio_spi_deinited(self)) { - return false; - } - return false; -} - -bool common_hal_busio_spi_has_lock(busio_spi_obj_t *self) { - return false; -} - -void common_hal_busio_spi_unlock(busio_spi_obj_t *self) { -} - - -bool common_hal_busio_spi_write(busio_spi_obj_t *self, - const uint8_t *data, size_t len) { - return false; -} - -bool common_hal_busio_spi_read(busio_spi_obj_t *self, - uint8_t *data, size_t len, uint8_t write_value) { - return false; -} - -bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len) { - return false; -} - -uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t *self) { - return 0; -} - -uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self) { - return 0; -} - -uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) { - return 0; -} diff --git a/ports/renode/common-hal/busio/SPI.h b/ports/renode/common-hal/busio/SPI.h deleted file mode 100644 index 32e5b0c3866..00000000000 --- a/ports/renode/common-hal/busio/SPI.h +++ /dev/null @@ -1,13 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; -} busio_spi_obj_t; diff --git a/ports/renode/common-hal/busio/UART.c b/ports/renode/common-hal/busio/UART.c deleted file mode 100644 index dc71a982e4b..00000000000 --- a/ports/renode/common-hal/busio/UART.c +++ /dev/null @@ -1,87 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 microDev -// -// SPDX-License-Identifier: MIT - -#include "shared-bindings/busio/UART.h" -#include "py/runtime.h" - -#define RXTX ((volatile uint32_t *)0x40000004) -#define NO_CHAR (0xffffffff) - -void common_hal_busio_uart_construct(busio_uart_obj_t *self, - const mcu_pin_obj_t *tx, const mcu_pin_obj_t *rx, - const mcu_pin_obj_t *rts, const mcu_pin_obj_t *cts, - const mcu_pin_obj_t *rs485_dir, bool rs485_invert, - uint32_t baudrate, uint8_t bits, busio_uart_parity_t parity, uint8_t stop, - mp_float_t timeout, uint16_t receiver_buffer_size, byte *receiver_buffer, - bool sigint_enabled) { - self->pending_char = NO_CHAR; -} - -bool common_hal_busio_uart_deinited(busio_uart_obj_t *self) { - return true; -} - -void common_hal_busio_uart_deinit(busio_uart_obj_t *self) { - if (common_hal_busio_uart_deinited(self)) { - return; - } -} - -// Write characters. -size_t common_hal_busio_uart_write(busio_uart_obj_t *self, const uint8_t *data, size_t len, int *errcode) { - for (size_t i = 0; i < len; i++) { - *RXTX = data[i]; - } - return len; -} - -// Read characters. -size_t common_hal_busio_uart_read(busio_uart_obj_t *self, uint8_t *data, size_t len, int *errcode) { - size_t read = 0; - while (self->pending_char != NO_CHAR && read < len) { - data[read] = self->pending_char; - read++; - self->pending_char = *RXTX; - } - return read; -} - -uint32_t common_hal_busio_uart_get_baudrate(busio_uart_obj_t *self) { - return 0; -} - -void common_hal_busio_uart_set_baudrate(busio_uart_obj_t *self, uint32_t baudrate) { - mp_raise_NotImplementedError(NULL); -} - -mp_float_t common_hal_busio_uart_get_timeout(busio_uart_obj_t *self) { - return 0; -} - -void common_hal_busio_uart_set_timeout(busio_uart_obj_t *self, mp_float_t timeout) { -} - -uint32_t common_hal_busio_uart_rx_characters_available(busio_uart_obj_t *self) { - if (self->pending_char != NO_CHAR) { - return 1; - } - self->pending_char = *RXTX; - if (self->pending_char != NO_CHAR) { - return 1; - } - return 0; -} - -void common_hal_busio_uart_clear_rx_buffer(busio_uart_obj_t *self) { - self->pending_char = NO_CHAR; -} - -bool common_hal_busio_uart_ready_to_tx(busio_uart_obj_t *self) { - return true; -} - -void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { -} diff --git a/ports/renode/common-hal/busio/UART.h b/ports/renode/common-hal/busio/UART.h deleted file mode 100644 index 6f2619954c6..00000000000 --- a/ports/renode/common-hal/busio/UART.h +++ /dev/null @@ -1,14 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 microDev -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; - uint32_t pending_char; -} busio_uart_obj_t; diff --git a/ports/renode/common-hal/busio/__init__.c b/ports/renode/common-hal/busio/__init__.c deleted file mode 100644 index b726684324a..00000000000 --- a/ports/renode/common-hal/busio/__init__.c +++ /dev/null @@ -1,7 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -// No busio module functions. diff --git a/ports/renode/common-hal/microcontroller/Pin.c b/ports/renode/common-hal/microcontroller/Pin.c deleted file mode 100644 index 03903343556..00000000000 --- a/ports/renode/common-hal/microcontroller/Pin.c +++ /dev/null @@ -1,59 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/runtime.h" - -#include "shared-bindings/microcontroller/Pin.h" - -void reset_all_pins(void) { -} - -void never_reset_pin_number(uint8_t pin_number) { -} - -void reset_pin_number(uint8_t pin_number) { -} - -void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - never_reset_pin_number(pin->number); -} - -void common_hal_reset_pin(const mcu_pin_obj_t *pin) { - reset_pin_number(pin->number); -} - -void claim_pin(const mcu_pin_obj_t *pin) { -} - -bool pin_number_is_free(uint8_t pin_number) { - return true; -} - -bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { - return pin_number_is_free(pin->number); -} - -uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin) { - return pin->number; -} - -void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { - return claim_pin(pin); -} - -void common_hal_mcu_pin_reset_number(uint8_t pin_no) { - reset_pin_number(pin_no); -} - -// This macro is used to simplify pin definition in boards//pins.c -#define PIN(p_number) \ - const mcu_pin_obj_t pin_GPIO##p_number = { \ - { &mcu_pin_type }, \ - .number = p_number \ - } - -PIN(0); -PIN(1); diff --git a/ports/renode/common-hal/microcontroller/Pin.h b/ports/renode/common-hal/microcontroller/Pin.h deleted file mode 100644 index 5e42c5920ac..00000000000 --- a/ports/renode/common-hal/microcontroller/Pin.h +++ /dev/null @@ -1,34 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include -#include - -#include - -typedef struct { - mp_obj_base_t base; - uint8_t number; -} mcu_pin_obj_t; - -extern const mcu_pin_obj_t pin_GPIO0; -extern const mcu_pin_obj_t pin_GPIO1; - -// If a board needs a different reset state for one or more pins, implement -// board_reset_pin_number so that it sets this state and returns `true` for those -// pin numbers, `false` for others. -// A default weak implementation always returns `false`. -bool board_reset_pin_number(uint8_t pin_number); - -void reset_all_pins(void); -// reset_pin_number takes the pin number instead of the pointer so that objects don't -// need to store a full pointer. -void reset_pin_number(uint8_t pin_number); -void never_reset_pin_number(uint8_t pin_number); -void claim_pin(const mcu_pin_obj_t *pin); -bool pin_number_is_free(uint8_t pin_number); diff --git a/ports/renode/common-hal/microcontroller/Processor.c b/ports/renode/common-hal/microcontroller/Processor.c deleted file mode 100644 index 3a6523338fc..00000000000 --- a/ports/renode/common-hal/microcontroller/Processor.c +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include -#include - -#include "common-hal/microcontroller/Processor.h" -#include "py/runtime.h" -#include "shared-bindings/microcontroller/Processor.h" -#include "shared-bindings/microcontroller/ResetReason.h" - -float common_hal_mcu_processor_get_temperature(void) { - return NAN; -} - -float common_hal_mcu_processor_get_voltage(void) { - return NAN; -} - -uint32_t common_hal_mcu_processor_get_frequency(void) { - return 100000000; -} - -void common_hal_mcu_processor_set_frequency(mcu_processor_obj_t *self, uint32_t frequency) { - mp_raise_NotImplementedError(NULL); -} - -void common_hal_mcu_processor_get_uid(uint8_t raw_id[]) { - raw_id[0] = 0xaf; -} - -mcu_reset_reason_t common_hal_mcu_processor_get_reset_reason(void) { - return MCU_RESET_REASON_POWER_ON; -} diff --git a/ports/renode/common-hal/microcontroller/Processor.h b/ports/renode/common-hal/microcontroller/Processor.h deleted file mode 100644 index 685a452535a..00000000000 --- a/ports/renode/common-hal/microcontroller/Processor.h +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define COMMON_HAL_MCU_PROCESSOR_UID_LENGTH 1 - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; - // Stores no state currently. -} mcu_processor_obj_t; diff --git a/ports/renode/common-hal/microcontroller/__init__.c b/ports/renode/common-hal/microcontroller/__init__.c deleted file mode 100644 index 47d7f15b825..00000000000 --- a/ports/renode/common-hal/microcontroller/__init__.c +++ /dev/null @@ -1,114 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/mphal.h" -#include "py/obj.h" - -#include "common-hal/microcontroller/__init__.h" -#include "shared-bindings/microcontroller/__init__.h" -#include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/microcontroller/Processor.h" -#include "supervisor/filesystem.h" -#include "supervisor/port.h" -#include "supervisor/shared/safe_mode.h" - -void common_hal_mcu_delay_us(uint32_t delay) { - mp_hal_delay_us(delay); -} - -volatile uint32_t nesting_count = 0; -void common_hal_mcu_disable_interrupts(void) { - // We don't use save_and_disable_interrupts() from the sdk because we don't want to worry about PRIMASK. - // This is what we do on the SAMD21 via CMSIS. - asm volatile ("cpsid i" : : : "memory"); - // __dmb(); - nesting_count++; -} - -void common_hal_mcu_enable_interrupts(void) { - if (nesting_count == 0) { - // reset_into_safe_mode(SAFE_MODE_INTERRUPT_ERROR); - } - nesting_count--; - if (nesting_count > 0) { - return; - } - // __dmb(); - asm volatile ("cpsie i" : : : "memory"); -} - -static bool next_reset_to_bootloader = false; - -void common_hal_mcu_on_next_reset(mcu_runmode_t runmode) { - switch (runmode) { - case RUNMODE_UF2: - case RUNMODE_BOOTLOADER: - next_reset_to_bootloader = true; - break; - case RUNMODE_SAFE_MODE: - safe_mode_on_next_reset(SAFE_MODE_PROGRAMMATIC); - break; - default: - break; - } -} - -void common_hal_mcu_reset(void) { - filesystem_flush(); - if (next_reset_to_bootloader) { - reset_to_bootloader(); - } else { - reset_cpu(); - } -} - -// The singleton microcontroller.Processor object, bound to microcontroller.cpu -// It currently only has properties, and no state. -#if CIRCUITPY_PROCESSOR_COUNT > 1 -static const mcu_processor_obj_t processor0 = { - .base = { - .type = &mcu_processor_type, - }, -}; - -static const mcu_processor_obj_t processor1 = { - .base = { - .type = &mcu_processor_type, - }, -}; - -const mp_rom_obj_tuple_t common_hal_multi_processor_obj = { - {&mp_type_tuple}, - CIRCUITPY_PROCESSOR_COUNT, - { - MP_ROM_PTR(&processor0), - MP_ROM_PTR(&processor1) - } -}; -#endif - -const mcu_processor_obj_t common_hal_mcu_processor_obj = { - .base = { - .type = &mcu_processor_type, - }, -}; - -// This maps MCU pin names to pin objects. -const mp_rom_map_elem_t mcu_pin_global_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_GPIO0), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_GPIO1), MP_ROM_PTR(&pin_GPIO1) }, -}; -MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_global_dict_table); - -const mcu_pin_obj_t *mcu_get_pin_by_number(int number) { - for (size_t i = 0; i < MP_ARRAY_SIZE(mcu_pin_global_dict_table); i++) { - mcu_pin_obj_t *obj = MP_OBJ_TO_PTR(mcu_pin_global_dict_table[i].value); - if (obj->base.type == &mcu_pin_type && obj->number == number) { - return obj; - } - } - return NULL; -} diff --git a/ports/renode/common-hal/microcontroller/__init__.h b/ports/renode/common-hal/microcontroller/__init__.h deleted file mode 100644 index 82f9558de0a..00000000000 --- a/ports/renode/common-hal/microcontroller/__init__.h +++ /dev/null @@ -1,11 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#include "shared-bindings/microcontroller/Pin.h" - -const mcu_pin_obj_t *mcu_get_pin_by_number(int); diff --git a/ports/renode/common-hal/os/__init__.c b/ports/renode/common-hal/os/__init__.c deleted file mode 100644 index 14e22960469..00000000000 --- a/ports/renode/common-hal/os/__init__.c +++ /dev/null @@ -1,17 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "genhdr/mpversion.h" -#include "py/mpconfig.h" -#include "py/objstr.h" -#include "py/objtuple.h" -#include "py/qstr.h" - -#include "shared-bindings/os/__init__.h" - -bool common_hal_os_urandom(uint8_t *buffer, mp_uint_t length) { - return false; -} diff --git a/ports/renode/link.ld b/ports/renode/link.ld deleted file mode 100644 index 125c0b0a009..00000000000 --- a/ports/renode/link.ld +++ /dev/null @@ -1,65 +0,0 @@ -MEMORY -{ - FLASH_FIRMWARE (rx) : ORIGIN = 0x00000000, LENGTH = 512k - RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128k -} - -SECTIONS -{ - .irqs : { - . = ALIGN(4); - KEEP(*(.rodata.interrupt_table .rodata.interrupt_table.*)) - . = ALIGN(4); - } > FLASH_FIRMWARE - - .text : { - *(.text*) - . = ALIGN(4); - } > FLASH_FIRMWARE - - .rodata : { - *(.rodata*) - . = ALIGN(4); - } > FLASH_FIRMWARE - - .ARM.extab : - { - *(.ARM.extab* .gnu.linkonce.armextab.*) - } > FLASH_FIRMWARE - - __exidx_start = .; - .ARM.exidx : - { - *(.ARM.exidx* .gnu.linkonce.armexidx.*) - } > FLASH_FIRMWARE - __exidx_end = .; - - /* End of .text-like segments */ - __etext = .; - - .data : { - __data_start__ = .; - *(.data*) - /* All data end */ - __data_end__ = .; - } > RAM AT> FLASH_FIRMWARE - _ld_ocram_data_destination = ADDR(.data); - _ld_ocram_data_flash_copy = LOADADDR(.data); - _ld_ocram_data_size = SIZEOF(.data); - - /* Uninitialized data section */ - .bss : { - . = ALIGN(4); - __bss_start__ = .; - *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) - *(COMMON) - . = ALIGN(4); - __bss_end__ = .; - } > RAM - _ld_ocram_bss_start = ADDR(.bss); - _ld_ocram_bss_size = SIZEOF(.bss); - _ld_heap_start = _ld_ocram_bss_start + _ld_ocram_bss_size; - _ld_ocram_start = ORIGIN(RAM); - _ld_ocram_end = ORIGIN(RAM) + LENGTH(RAM); - _ld_heap_end = _ld_ocram_end; -} diff --git a/ports/renode/mpconfigport.h b/ports/renode/mpconfigport.h deleted file mode 100644 index 185a884ed31..00000000000 --- a/ports/renode/mpconfigport.h +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -#define MICROPY_PY_SYS_PLATFORM "Renode" - -#define MICROPY_USE_INTERNAL_PRINTF (1) - -// This also includes mpconfigboard.h. -#include "py/circuitpy_mpconfig.h" - -#define CIRCUITPY_DEFAULT_STACK_SIZE (24 * 1024) diff --git a/ports/renode/mpconfigport.mk b/ports/renode/mpconfigport.mk deleted file mode 100644 index e2171c5b6c0..00000000000 --- a/ports/renode/mpconfigport.mk +++ /dev/null @@ -1,40 +0,0 @@ -LONGINT_IMPL = MPZ -INTERNAL_LIBM = 1 - -# We build .elf files to run in the simulator. Generally, this will be a file -# type supported by the chip family's bootloader. -CIRCUITPY_BUILD_EXTENSIONS = elf - -# The port manages flash itself instead of using SPI flash via busio.SPI. -INTERNAL_FLASH_FILESYSTEM = 1 - -# Usually lots of flash space available -CIRCUITPY_MESSAGE_COMPRESSION_LEVEL = 1 - -# Disable modules included in full builds -CIRCUITPY_FULL_BUILD = 0 - -# Most ports will want to enable this early to use the USB workflow in testing -# module implementation. It depends on TinyUSB supporting the USB peripheral. -# Renode uses UART instead of USB. -CIRCUITPY_USB_DEVICE = 0 - -# Disable modules included in slim builds directly. Enable these as they are -# implemented. -CIRCUITPY_ANALOGBUFIO = 0 -CIRCUITPY_ANALOGIO = 0 -CIRCUITPY_BUSIO_I2C = 0 -CIRCUITPY_BUSIO_SPI = 0 -CIRCUITPY_DIGITALIO = 0 -CIRCUITPY_NEOPIXEL_WRITE = 0 -CIRCUITPY_NVM = 0 -CIRCUITPY_ONEWIREIO = 0 -CIRCUITPY_PWMIO = 0 -CIRCUITPY_RANDOM = 0 -CIRCUITPY_ROTARYIO = 0 -CIRCUITPY_ROTARYIO_SOFTENCODER = 0 -CIRCUITPY_RTC = 0 -CIRCUITPY_STORAGE = 0 -CIRCUITPY_TOUCHIO = 0 - -CIRCUITPY_BITBANGIO = $(CIRCUITPY_DIGITALIO) diff --git a/ports/renode/mphalport.c b/ports/renode/mphalport.c deleted file mode 100644 index b201420d9bb..00000000000 --- a/ports/renode/mphalport.c +++ /dev/null @@ -1,25 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "py/mphal.h" -#include "shared-bindings/microcontroller/__init__.h" - -#include "mpconfigboard.h" -#include "mphalport.h" - -extern uint32_t common_hal_mcu_processor_get_frequency(void); - -void mp_hal_delay_us(mp_uint_t delay) { - // TODO busy_wait_us_32(delay); -} - -void mp_hal_disable_all_interrupts(void) { - common_hal_mcu_disable_interrupts(); -} - -void mp_hal_enable_all_interrupts(void) { - common_hal_mcu_enable_interrupts(); -} diff --git a/ports/renode/mphalport.h b/ports/renode/mphalport.h deleted file mode 100644 index 802442dbaaf..00000000000 --- a/ports/renode/mphalport.h +++ /dev/null @@ -1,15 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#pragma once - -// Global millisecond tick count (driven by SysTick interrupt). -#define mp_hal_ticks_ms() ((mp_uint_t)supervisor_ticks_ms32()) - -void mp_hal_set_interrupt_char(int c); - -void mp_hal_disable_all_interrupts(void); -void mp_hal_enable_all_interrupts(void); diff --git a/ports/renode/qstrdefsport.h b/ports/renode/qstrdefsport.h deleted file mode 100644 index a754f55f42a..00000000000 --- a/ports/renode/qstrdefsport.h +++ /dev/null @@ -1,9 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC -// -// SPDX-License-Identifier: MIT - -// Do not use #pragma once in this file; it will cause a warning during qstr generation. - -// qstrs specific to this port diff --git a/ports/renode/renode.resc b/ports/renode/renode.resc deleted file mode 100644 index 11c95e08419..00000000000 --- a/ports/renode/renode.resc +++ /dev/null @@ -1,29 +0,0 @@ -using sysbus - -include @Simple32kHz.cs -emulation CreateUartPtyTerminal "term" "/tmp/cp-uart" - -$board?="renode_cortex_m0plus" -mach create $board - -machine LoadPlatformDescription $ORIGIN/boards/renode_cortex_m0plus/board.repl - -# uart RecordToAsciinema $ORIGIN/build-renode_cortex_m0plus/serial.asciinema -connector Connect sysbus.uart term - -sysbus LoadELF @build-renode_cortex_m0plus/firmware.elf -cpu VectorTableOffset `sysbus GetSymbolAddress "interrupt_table"` - -sysbus LoadBinary @build-renode_cortex_m0plus/circuitpy.img 0x10000000 - -# Enable all debug logging -# logLevel -1 - -# Enable function profiling -# cpu EnableProfiler CollapsedStack $ORIGIN/profile.folded true - -# Enable execution tracing -# cpu CreateExecutionTracing "tracer_name" $ORIGIN/instruction_trace.txt Disassembly - -# Enable the gdb server -# machine StartGdbServer 3333 true diff --git a/ports/renode/supervisor/internal_flash.c b/ports/renode/supervisor/internal_flash.c deleted file mode 100644 index 028258aee35..00000000000 --- a/ports/renode/supervisor/internal_flash.c +++ /dev/null @@ -1,48 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include "supervisor/internal_flash.h" - -#include -#include -#include - -#include "supervisor/flash.h" - -#define FLASH_BASE 0x10000000 -#define FLASH_SIZE 0x80000 - -void supervisor_flash_init(void) { -} - -uint32_t supervisor_flash_get_block_size(void) { - return FILESYSTEM_BLOCK_SIZE; -} - -uint32_t supervisor_flash_get_block_count(void) { - return FLASH_SIZE / FILESYSTEM_BLOCK_SIZE; -} - -void port_internal_flash_flush(void) { -} - -mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) { - memcpy(dest, - (void *)(FLASH_BASE + block * FILESYSTEM_BLOCK_SIZE), - num_blocks * FILESYSTEM_BLOCK_SIZE); - return 0; -} - -mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t lba, uint32_t num_blocks) { - memcpy((void *)(FLASH_BASE + lba * FILESYSTEM_BLOCK_SIZE), - src, - num_blocks * FILESYSTEM_BLOCK_SIZE); - - return 0; // success -} - -void supervisor_flash_release_cache(void) { -} diff --git a/ports/renode/supervisor/internal_flash.h b/ports/renode/supervisor/internal_flash.h deleted file mode 100644 index f85b4f7a24b..00000000000 --- a/ports/renode/supervisor/internal_flash.h +++ /dev/null @@ -1,8 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT -#pragma once - -#include diff --git a/ports/renode/supervisor/port.c b/ports/renode/supervisor/port.c deleted file mode 100644 index a40e222d34d..00000000000 --- a/ports/renode/supervisor/port.c +++ /dev/null @@ -1,217 +0,0 @@ -// This file is part of the CircuitPython project: https://circuitpython.org -// -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries -// -// SPDX-License-Identifier: MIT - -#include -#include -#include - -#include "supervisor/background_callback.h" -#include "supervisor/port.h" - -#include "genhdr/mpversion.h" -#include "shared-bindings/microcontroller/__init__.h" - -#include "supervisor/shared/safe_mode.h" - - -extern uint32_t _ld_flash_size; -extern uint32_t _ld_stack_top; - -extern uint32_t __isr_vector[]; - -extern uint32_t _ld_ocram_start; -extern uint32_t _ld_ocram_bss_start; -extern uint32_t _ld_ocram_bss_size; -extern uint32_t _ld_ocram_data_destination; -extern uint32_t _ld_ocram_data_size; -extern uint32_t _ld_ocram_data_flash_copy; -extern uint32_t _ld_ocram_end; -extern uint32_t _ld_dtcm_bss_start; -extern uint32_t _ld_dtcm_bss_size; -extern uint32_t _ld_dtcm_data_destination; -extern uint32_t _ld_dtcm_data_size; -extern uint32_t _ld_dtcm_data_flash_copy; -extern uint32_t _ld_itcm_destination; -extern uint32_t _ld_itcm_size; -extern uint32_t _ld_itcm_flash_copy; -extern uint32_t _ld_isr_destination; -extern uint32_t _ld_isr_size; -extern uint32_t _ld_isr_flash_copy; - -extern void main(void); - -// This replaces the Reset_Handler in startup_*.S and SystemInit in system_*.c. -// Turn off optimize("no-tree-loop-distribute-patterns") so that this isn't replaced -// by calls to memcpy because we're copying it over now. -void Reset_Handler(void); -__attribute__((used, naked, no_instrument_function, optimize("no-tree-loop-distribute-patterns"))) void Reset_Handler(void) { - // Copy all of the itcm code to run from ITCM. Do this while the MPU is disabled because we write - // protect it. - // for (uint32_t i = 0; i < ((size_t)&_ld_itcm_size) / 4; i++) { - // (&_ld_itcm_destination)[i] = (&_ld_itcm_flash_copy)[i]; - // } - - // for (uint32_t i = 0; i < ((size_t)&_ld_isr_size) / 4; i++) { - // (&_ld_isr_destination)[i] = (&_ld_isr_flash_copy)[i]; - // } - - // Copy all of the data to run from DTCM. - // for (uint32_t i = 0; i < ((size_t)&_ld_dtcm_data_size) / 4; i++) { - // (&_ld_dtcm_data_destination)[i] = (&_ld_dtcm_data_flash_copy)[i]; - // } - - // // Clear DTCM bss. - // for (uint32_t i = 0; i < ((size_t)&_ld_dtcm_bss_size) / 4; i++) { - // (&_ld_dtcm_bss_start)[i] = 0; - // } - - // Copy all of the data to run from OCRAM. - for (uint32_t i = 0; i < ((size_t)&_ld_ocram_data_size) / 4; i++) { - (&_ld_ocram_data_destination)[i] = (&_ld_ocram_data_flash_copy)[i]; - } - - // Clear OCRAM bss. - for (uint32_t i = 0; i < ((size_t)&_ld_ocram_bss_size) / 4; i++) { - (&_ld_ocram_bss_start)[i] = 0; - } - - main(); -} - -typedef void (*isr_vector)(void); -const isr_vector __attribute__((used)) interrupt_table[17] = { - (isr_vector) & _ld_ocram_end, - Reset_Handler, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL -}; - -safe_mode_t __attribute__((used)) port_init(void) { - // Reset everything into a known state before board_init. - reset_port(); - - return SAFE_MODE_NONE; -} - -void reset_port(void) { - // Older ports will do blanket resets here. Instead, move to a model that - // uses the deinit() functions to reset internal state. -} - -void reset_to_bootloader(void) { - while (true) { - } -} - -void reset_cpu(void) { - while (true) { - // __wfi(); - } -} - -// From the linker script -extern uint32_t _ld_heap_start; -extern uint32_t _ld_heap_end; -uint32_t *port_stack_get_limit(void) { - #pragma GCC diagnostic push - - #pragma GCC diagnostic ignored "-Warray-bounds" - return port_stack_get_top() - (CIRCUITPY_DEFAULT_STACK_SIZE + CIRCUITPY_EXCEPTION_STACK_SIZE) / sizeof(uint32_t); - #pragma GCC diagnostic pop -} - -uint32_t *port_stack_get_top(void) { - return &_ld_heap_end; -} - -uint32_t *port_heap_get_bottom(void) { - return &_ld_heap_start; -} - -uint32_t *port_heap_get_top(void) { - return port_stack_get_limit(); -} - -uint32_t saved_word; -void port_set_saved_word(uint32_t value) { - // Store in RAM because the watchdog scratch registers don't survive - // resetting by pulling the RUN pin low. - saved_word = value; -} - -uint32_t port_get_saved_word(void) { - return saved_word; -} - -static volatile bool ticks_enabled; -static volatile bool _woken_up; - -uint64_t port_get_raw_ticks(uint8_t *subticks) { - volatile uint32_t *ticks_32khz = ((volatile uint32_t *)0x40001000); - uint32_t total_ticks = *ticks_32khz; - if (subticks != NULL) { - *subticks = total_ticks % 32; - } - return total_ticks / 32; -} - -// Enable 1/1024 second tick. -void port_enable_tick(void) { - ticks_enabled = true; -} - -// Disable 1/1024 second tick. -void port_disable_tick(void) { - // One additional _tick_callback may occur, but it will just return - // whenever !ticks_enabled. Cancel is not called just in case - // it could nuke a timeout set by port_interrupt_after_ticks. - ticks_enabled = false; -} - -// This is called by sleep, we ignore it when our ticks are enabled because -// they'll wake us up earlier. If we don't, we'll mess up ticks by overwriting -// the next RTC wake up time. -void port_interrupt_after_ticks(uint32_t ticks) { - _woken_up = false; -} - -void port_idle_until_interrupt(void) { - common_hal_mcu_disable_interrupts(); - if (!background_callback_pending() && !_woken_up) { - // __DSB(); - // __WFI(); - } - common_hal_mcu_enable_interrupts(); -} - -/** - * \brief Default interrupt handler for unused IRQs. - */ -extern void HardFault_Handler(void); // provide a prototype to avoid a missing-prototypes diagnostic -__attribute__((used)) void HardFault_Handler(void) { - while (true) { - asm ("nop;"); - } -} - -void port_task_yield(void) { -} - -void port_boot_info(void) { -} diff --git a/ports/renode/tools/split_profile.py b/ports/renode/tools/split_profile.py deleted file mode 100644 index 3c3ed153ec6..00000000000 --- a/ports/renode/tools/split_profile.py +++ /dev/null @@ -1,24 +0,0 @@ -# Split a profile file into smaller chunks. - -import pathlib -import sys - -input_file = pathlib.Path(sys.argv[-1]) - -supervisor = input_file.with_suffix(".supervisor" + input_file.suffix) -boot_py = input_file.with_suffix(".boot_py" + input_file.suffix) -code_py = input_file.with_suffix(".code_py" + input_file.suffix) - -supervisor_f = supervisor.open("w") -boot_py_f = boot_py.open("w") -code_py_f = code_py.open("w") - -for line in input_file.open(): - if "run_boot_py" in line: - boot_py_f.write(line) - if "run_code_py" in line: - code_py_f.write(line) - -supervisor_f.close() -boot_py_f.close() -code_py_f.close() diff --git a/ports/silabs/common-hal/_bleio/Connection.c b/ports/silabs/common-hal/_bleio/Connection.c index 94e556dd0bf..4f5da679931 100644 --- a/ports/silabs/common-hal/_bleio/Connection.c +++ b/ports/silabs/common-hal/_bleio/Connection.c @@ -57,6 +57,21 @@ bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) { return self->connection->pair_status == PAIR_PAIRED; } +// Authenticated (MITM-protected) pairing - numeric comparison - is implemented on the +// espressif port only. This port can't tell a "Just Works" bond from an authenticated +// one, so report the conservative answer rather than a guarantee it can't make. +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + return false; +} + +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { + return mp_const_none; +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + mp_raise_NotImplementedError(NULL); +} + // Get connected status bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { if (self->connection == NULL) { diff --git a/ports/silabs/common-hal/analogio/AnalogOut.c b/ports/silabs/common-hal/analogio/AnalogOut.c index f3fd63a32ca..3ae125e850c 100644 --- a/ports/silabs/common-hal/analogio/AnalogOut.c +++ b/ports/silabs/common-hal/analogio/AnalogOut.c @@ -152,15 +152,3 @@ void common_hal_analogio_analogout_set_value(analogio_analogout_obj_t *self, // Write the output value to VDAC DATA register VDAC_ChannelOutputSet(self->dac->vdac, self->dac->channel, value >> 4); } - -// Function reset dac peripheral -void analogout_reset(void) { - uint8_t dac_index; - mcu_dac_pin_obj_t *p_dac; - for (dac_index = 0; dac_index < DAC_BANK_ARRAY_LEN; dac_index++) { - p_dac = &mcu_dac_list[dac_index]; - if (p_dac->is_used == true) { - VDAC_Reset(p_dac->vdac); - } - } -} diff --git a/ports/silabs/common-hal/analogio/AnalogOut.h b/ports/silabs/common-hal/analogio/AnalogOut.h index a8a8e8f9ae3..d5970be477c 100644 --- a/ports/silabs/common-hal/analogio/AnalogOut.h +++ b/ports/silabs/common-hal/analogio/AnalogOut.h @@ -37,6 +37,5 @@ typedef struct mcu_dac_pin_obj_t *dac; } analogio_analogout_obj_t; -void analogout_reset(void); #endif // MICROPY_INCLUDED_EFR32_COMMON_HAL_ANALOGIO_ANALOGOUT_H diff --git a/ports/silabs/common-hal/busio/I2C.c b/ports/silabs/common-hal/busio/I2C.c index 6c2b04d821d..34b288be746 100644 --- a/ports/silabs/common-hal/busio/I2C.c +++ b/ports/silabs/common-hal/busio/I2C.c @@ -72,12 +72,6 @@ void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, } } -// Never reset I2C obj when reload -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - common_hal_never_reset_pin(self->sda); - common_hal_never_reset_pin(self->scl); -} - // Check I2C status, deinited or not bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { return self->sda == NULL; @@ -91,7 +85,6 @@ void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) { I2C_Reset(self->i2cspm); common_hal_reset_pin(self->sda); common_hal_reset_pin(self->scl); - self->i2cspm = NULL; in_used = false; common_hal_busio_i2c_mark_deinit(self); } diff --git a/ports/silabs/common-hal/busio/SPI.c b/ports/silabs/common-hal/busio/SPI.c index fb3b7c4fd20..a09ff092f25 100644 --- a/ports/silabs/common-hal/busio/SPI.c +++ b/ports/silabs/common-hal/busio/SPI.c @@ -99,13 +99,6 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, common_hal_mcu_pin_claim(miso); } -// Never reset SPI when reload -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { - common_hal_never_reset_pin(self->mosi); - common_hal_never_reset_pin(self->miso); - common_hal_never_reset_pin(self->sck); -} - // Check SPI status, deinited or not bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { return self->sck == NULL; @@ -122,19 +115,17 @@ void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { return; } + common_hal_reset_pin(self->mosi); + common_hal_reset_pin(self->miso); + common_hal_reset_pin(self->sck); + Ecode_t sc = SPIDRV_DeInit(self->handle); - if (sc != ECODE_EMDRV_SPIDRV_OK) { - mp_raise_RuntimeError(MP_ERROR_TEXT("SPI re-init")); - } + (void)sc; // Ignore errors: deinit must not raise when run from __del__. in_used = false; self->mosi = NULL; self->miso = NULL; self->handle = NULL; - common_hal_reset_pin(self->mosi); - common_hal_reset_pin(self->miso); - common_hal_reset_pin(self->sck); - common_hal_busio_spi_mark_deinit(self); } diff --git a/ports/silabs/common-hal/busio/UART.c b/ports/silabs/common-hal/busio/UART.c index cfc87b6b6f8..d0e8695f83a 100644 --- a/ports/silabs/common-hal/busio/UART.c +++ b/ports/silabs/common-hal/busio/UART.c @@ -45,20 +45,9 @@ DEFINE_BUF_QUEUE(UARTDRV_USART_BUFFER_SIZE, uartdrv_usart_tx_buffer); static UARTDRV_HandleData_t uartdrv_usart_handle; static UARTDRV_InitUart_t uartdrv_usart_init; static bool in_used = false; -static bool never_reset = false; busio_uart_obj_t *context; volatile Ecode_t errflag; // Used to restart read halts -// Reset uart peripheral -void uart_reset(void) { - if ((!never_reset) && in_used) { - if (UARTDRV_DeInit(&uartdrv_usart_handle) != ECODE_EMDRV_UARTDRV_OK) { - mp_raise_ValueError(MP_ERROR_TEXT("UART Deinit fail")); - } - in_used = false; - } -} - // Construct uart obj void common_hal_busio_uart_construct(busio_uart_obj_t *self, const mcu_pin_obj_t *tx, @@ -138,14 +127,6 @@ void common_hal_busio_uart_construct(busio_uart_obj_t *self, } } -// Never reset UART obj when reload -void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { - never_reset = true; - common_hal_never_reset_pin(self->tx); - common_hal_never_reset_pin(self->rx); - return; -} - // Check Uart status, deinited or not bool common_hal_busio_uart_deinited(busio_uart_obj_t *self) { return self->handle == NULL; diff --git a/ports/silabs/common-hal/busio/UART.h b/ports/silabs/common-hal/busio/UART.h index 50d43d5cd67..a91dda74165 100644 --- a/ports/silabs/common-hal/busio/UART.h +++ b/ports/silabs/common-hal/busio/UART.h @@ -47,6 +47,5 @@ typedef struct { bool sigint_enabled; } busio_uart_obj_t; -void uart_reset(void); #endif // MICROPY_INCLUDED_EFR32_COMMON_HAL_BUSIO_UART_H diff --git a/ports/silabs/common-hal/digitalio/DigitalInOut.c b/ports/silabs/common-hal/digitalio/DigitalInOut.c index e9f2767308e..1f9a20f8fab 100644 --- a/ports/silabs/common-hal/digitalio/DigitalInOut.c +++ b/ports/silabs/common-hal/digitalio/DigitalInOut.c @@ -28,12 +28,6 @@ #include "shared-bindings/microcontroller/Pin.h" #include "py/runtime.h" -// Never reset pin when reload -void common_hal_digitalio_digitalinout_never_reset( - digitalio_digitalinout_obj_t *self) { - common_hal_never_reset_pin(self->pin); -} - // Construct Digitalio obj digitalinout_result_t common_hal_digitalio_digitalinout_construct( digitalio_digitalinout_obj_t *self, const mcu_pin_obj_t *pin) { diff --git a/ports/silabs/common-hal/microcontroller/Pin.c b/ports/silabs/common-hal/microcontroller/Pin.c index 249bc5ac482..5b322318e4e 100644 --- a/ports/silabs/common-hal/microcontroller/Pin.c +++ b/ports/silabs/common-hal/microcontroller/Pin.c @@ -33,48 +33,14 @@ GPIO_Port_TypeDef ports[] = {gpioPortA, gpioPortB, gpioPortC, gpioPortD}; static uint16_t claimed_pins[GPIO_PORT_COUNT]; -static uint16_t __ALIGNED(4) never_reset_pins[GPIO_PORT_COUNT]; - -// Reset all pin except pin in never_reset_pins list -void reset_all_pins(void) { - - uint8_t pin_num; - uint8_t port_num; - // Reset claimed pins - for (pin_num = 0; pin_num < GPIO_PORT_COUNT; pin_num++) { - claimed_pins[pin_num] = never_reset_pins[pin_num]; - } - - for (port_num = 0; port_num < GPIO_PORT_COUNT; port_num++) { - for (pin_num = 0; pin_num < 16; pin_num++) { - if (GPIO_PORT_PIN_VALID(ports[port_num], pin_num) - && !(never_reset_pins[port_num] >> pin_num & 0x01)) { - GPIO_PinModeSet(ports[port_num], pin_num, gpioModeInput, 1); - } - } - } -} // Mark pin as free and return it to a quiescent state. void reset_pin_number(uint8_t pin_port, uint8_t pin_number) { // Clear claimed bit & reset claimed_pins[pin_port] &= ~(1 << pin_number); - never_reset_pins[pin_port] &= ~(1 << pin_number); GPIO_PinModeSet(pin_port, pin_number, gpioModeInput, 1); } -// Mark pin as never reset -void never_reset_pin_number(uint8_t pin_port, uint8_t pin_number) { - never_reset_pins[pin_port] |= 1 << pin_number; - // Make sure never reset pins are also always claimed - claimed_pins[pin_port] |= 1 << pin_number; -} - -// Mark pin as never reset -void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - never_reset_pin_number(pin->port, pin->number); -} - // Reset pin void common_hal_reset_pin(const mcu_pin_obj_t *pin) { if (pin == NULL) { diff --git a/ports/silabs/common-hal/microcontroller/Pin.h b/ports/silabs/common-hal/microcontroller/Pin.h index 20bf8a82f2b..4c868a2d09b 100644 --- a/ports/silabs/common-hal/microcontroller/Pin.h +++ b/ports/silabs/common-hal/microcontroller/Pin.h @@ -31,6 +31,9 @@ #include "peripherals/pins.h" -void reset_all_pins(void); +void common_hal_reset_pin(const mcu_pin_obj_t *pin); + +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); +void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin); #endif // MICROPY_INCLUDED_EFR32_COMMON_HAL_MICROCONTROLLER_PIN_H diff --git a/ports/silabs/common-hal/pwmio/PWMOut.c b/ports/silabs/common-hal/pwmio/PWMOut.c index 495199360f7..844b8966a00 100644 --- a/ports/silabs/common-hal/pwmio/PWMOut.c +++ b/ports/silabs/common-hal/pwmio/PWMOut.c @@ -88,15 +88,6 @@ pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self, return PWMOUT_OK; } -// Mark pwm obj to never reset after reload -void common_hal_pwmio_pwmout_never_reset(pwmio_pwmout_obj_t *self) { - common_hal_never_reset_pin(self->tim->pin); -} - -// Pwm will be reset after reloading. -void common_hal_pwmio_pwmout_reset_ok(pwmio_pwmout_obj_t *self) { -} - // Check pwm obj status, deinited or not bool common_hal_pwmio_pwmout_deinited(pwmio_pwmout_obj_t *self) { return self->tim == NULL; diff --git a/ports/silabs/common-hal/rtc/RTC.h b/ports/silabs/common-hal/rtc/RTC.h index 12683a310a7..ac18a68dd63 100644 --- a/ports/silabs/common-hal/rtc/RTC.h +++ b/ports/silabs/common-hal/rtc/RTC.h @@ -28,6 +28,5 @@ #define MICROPY_INCLUDED_STM_COMMON_HAL_RTC_RTC_H extern void rtc_init(void); -extern void rtc_reset(void); #endif // MICROPY_INCLUDED_STM_COMMON_HAL_RTC_RTC_H diff --git a/ports/silabs/mpconfigport.mk b/ports/silabs/mpconfigport.mk index 7192be3f415..493651de3d1 100644 --- a/ports/silabs/mpconfigport.mk +++ b/ports/silabs/mpconfigport.mk @@ -40,3 +40,6 @@ endif CIRCUITPY_HASHLIB_MBEDTLS_ONLY = 0 CIRCUITPY_BUILD_EXTENSIONS ?= bin + +# Cleanup is performed by GC finalizers (__del__ -> deinit) instead of a bulk pin reset. +CIRCUITPY_BULK_RESET = 0 diff --git a/ports/silabs/supervisor/port.c b/ports/silabs/supervisor/port.c index 85a6e0f92a5..efca79bdcaa 100644 --- a/ports/silabs/supervisor/port.c +++ b/ports/silabs/supervisor/port.c @@ -156,14 +156,6 @@ safe_mode_t port_init(void) { } void reset_port(void) { - #if CIRCUITPY_BUSIO - uart_reset(); - #endif - - #if CIRCUITPY_ANALOGIO - analogout_reset(); - #endif - #if CIRCUITPY_BLEIO bleio_reset(); #endif diff --git a/ports/silabs/supervisor/serial.c b/ports/silabs/supervisor/serial.c index 25dc86c1da9..24d2c3b172e 100644 --- a/ports/silabs/supervisor/serial.c +++ b/ports/silabs/supervisor/serial.c @@ -102,11 +102,9 @@ void port_serial_early_init(void) { EUSART_UartInitHf(EUSART0, &init); - // Claim and never reset UART console pin + // Claim UART console pins common_hal_mcu_pin_claim(&pin_PA5); common_hal_mcu_pin_claim(&pin_PA6); - common_hal_never_reset_pin(&pin_PA5); - common_hal_never_reset_pin(&pin_PA6); } // Enable EUSART0 interrupt, init ring buffer diff --git a/ports/stm/boards/blues_cygnet/board.c b/ports/stm/boards/blues_cygnet/board.c index f6650fb8f9c..eddebad3f5d 100644 --- a/ports/stm/boards/blues_cygnet/board.c +++ b/ports/stm/boards/blues_cygnet/board.c @@ -27,8 +27,6 @@ void initialize_discharge_pin(void) { common_hal_digitalio_digitalinout_construct(&power_pin, &pin_PH00); common_hal_digitalio_digitalinout_construct(&discharge_pin, &pin_PH01); - common_hal_digitalio_digitalinout_never_reset(&power_pin); - common_hal_digitalio_digitalinout_never_reset(&discharge_pin); GPIO_InitTypeDef GPIO_InitStruct; diff --git a/ports/stm/boards/common_default.ld b/ports/stm/boards/common_default.ld index 7c96a42bc56..62a6d63d11c 100644 --- a/ports/stm/boards/common_default.ld +++ b/ports/stm/boards/common_default.ld @@ -35,6 +35,12 @@ SECTIONS .text : { . = ALIGN(4); + __property_getter_start = .; + *(.property_getter) + __property_getter_end = .; + __property_getset_start = .; + *(.property_getset) + __property_getset_end = .; *(.text*) /* .text* sections (code) */ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ /* *(.glue_7) */ /* glue arm to thumb code */ @@ -95,4 +101,9 @@ SECTIONS .ARM.attributes 0 : { *(.ARM.attributes) } + + ASSERT((__property_getter_end - __property_getter_start) % 8 == 0, + "the .property_getter section must hold whole 8-byte objects") + ASSERT((__property_getset_end - __property_getset_start) % 12 == 0, + "the .property_getset section must hold whole 12-byte objects") } diff --git a/ports/stm/boards/common_nvm.ld b/ports/stm/boards/common_nvm.ld index 1d10db8a314..f2a0456e000 100644 --- a/ports/stm/boards/common_nvm.ld +++ b/ports/stm/boards/common_nvm.ld @@ -45,6 +45,12 @@ SECTIONS .text : { . = ALIGN(4); + __property_getter_start = .; + *(.property_getter) + __property_getter_end = .; + __property_getset_start = .; + *(.property_getset) + __property_getset_end = .; *(.text*) /* .text* sections (code) */ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ /* *(.glue_7) */ /* glue arm to thumb code */ @@ -105,4 +111,9 @@ SECTIONS .ARM.attributes 0 : { *(.ARM.attributes) } + + ASSERT((__property_getter_end - __property_getter_start) % 8 == 0, + "the .property_getter section must hold whole 8-byte objects") + ASSERT((__property_getset_end - __property_getset_start) % 12 == 0, + "the .property_getset section must hold whole 12-byte objects") } diff --git a/ports/stm/boards/common_tcm.ld b/ports/stm/boards/common_tcm.ld index 6301dbf315b..18c284f965a 100644 --- a/ports/stm/boards/common_tcm.ld +++ b/ports/stm/boards/common_tcm.ld @@ -37,6 +37,12 @@ SECTIONS .text : { . = ALIGN(4); + __property_getter_start = .; + *(.property_getter) + __property_getter_end = .; + __property_getset_start = .; + *(.property_getset) + __property_getset_end = .; *(.text*) /* .text* sections (code) */ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ /* *(.glue_7) */ /* glue arm to thumb code */ @@ -146,4 +152,9 @@ SECTIONS } >FLASH_FIRMWARE .ARM.attributes 0 : { *(.ARM.attributes) } + + ASSERT((__property_getter_end - __property_getter_start) % 8 == 0, + "the .property_getter section must hold whole 8-byte objects") + ASSERT((__property_getset_end - __property_getset_start) % 12 == 0, + "the .property_getset section must hold whole 12-byte objects") } diff --git a/ports/stm/boards/meowbit_v121/board.c b/ports/stm/boards/meowbit_v121/board.c index c3c745f64a1..26fd0212dfc 100644 --- a/ports/stm/boards/meowbit_v121/board.c +++ b/ports/stm/boards/meowbit_v121/board.c @@ -8,6 +8,7 @@ #include "mpconfigboard.h" #include "shared-bindings/audiopwmio/PWMAudioOut.h" +#include "common-hal/microcontroller/Pin.h" #include "shared-bindings/board/__init__.h" #include "shared-module/displayio/__init__.h" #include "shared-module/displayio/mipi_constants.h" @@ -96,7 +97,8 @@ void board_init(void) { board_buzz_obj.base.type = &audiopwmio_pwmaudioout_type; common_hal_audiopwmio_pwmaudioout_construct(&board_buzz_obj, &pin_PB08, NULL, 0x8000); - never_reset_pin_number(pin_PB08.port, pin_PB08.number); + // Mark the buzzer pin claimed so user code can't conflict with it. + claim_pin(pin_PB08.port, pin_PB08.number); } // Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/stm/boards/stm32f746g_discovery/board.c b/ports/stm/boards/stm32f746g_discovery/board.c index 5b4f3470769..cd481e6597b 100644 --- a/ports/stm/boards/stm32f746g_discovery/board.c +++ b/ports/stm/boards/stm32f746g_discovery/board.c @@ -25,7 +25,8 @@ void board_init(void) { HAL_GPIO_Init(GPIOK, &GPIO_InitStructure); HAL_GPIO_WritePin(GPIOK, GPIO_PIN_3, GPIO_PIN_RESET); - never_reset_pin_number(10, 3); + // PK3 drives the LCD backlight; mark it claimed so user code can't conflict with it. + claim_pin(10, 3); } // Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/stm/boards/swan_r5/board.c b/ports/stm/boards/swan_r5/board.c index 690120bf5ac..a88c53b2389 100644 --- a/ports/stm/boards/swan_r5/board.c +++ b/ports/stm/boards/swan_r5/board.c @@ -28,8 +28,6 @@ void initialize_discharge_pin(void) { common_hal_digitalio_digitalinout_construct(&power_pin, &pin_PE04); common_hal_digitalio_digitalinout_construct(&discharge_pin, &pin_PE06); - common_hal_digitalio_digitalinout_never_reset(&power_pin); - common_hal_digitalio_digitalinout_never_reset(&discharge_pin); GPIO_InitTypeDef GPIO_InitStruct; /* Set the DISCHARGE pin and the USB_DETECT pin to FLOAT */ diff --git a/ports/stm/common-hal/alarm/pin/PinAlarm.c b/ports/stm/common-hal/alarm/pin/PinAlarm.c index 93512953d31..1cda14e0356 100644 --- a/ports/stm/common-hal/alarm/pin/PinAlarm.c +++ b/ports/stm/common-hal/alarm/pin/PinAlarm.c @@ -122,7 +122,6 @@ void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_ob // so we put it off until right before sleeping. deep_wkup_enabled = true; // EXTI needs to persist past the VM cleanup for fake deep sleep - stm_peripherals_exti_never_reset(alarm->pin->number); } if (!stm_peripherals_exti_reserve(alarm->pin->number)) { mp_raise_RuntimeError(MP_ERROR_TEXT("Pin interrupt already in use")); diff --git a/ports/stm/common-hal/analogio/AnalogOut.c b/ports/stm/common-hal/analogio/AnalogOut.c index 44fdff5af4b..665d749658d 100644 --- a/ports/stm/common-hal/analogio/AnalogOut.c +++ b/ports/stm/common-hal/analogio/AnalogOut.c @@ -84,6 +84,9 @@ bool common_hal_analogio_analogout_deinited(analogio_analogout_obj_t *self) { void common_hal_analogio_analogout_deinit(analogio_analogout_obj_t *self) { #if HAS_DAC + if (common_hal_analogio_analogout_deinited(self)) { + return; + } reset_pin_number(self->pin->port, self->pin->number); self->pin = NULL; dac_on[self->dac_index] = false; diff --git a/ports/stm/common-hal/audioio/AudioOut.c b/ports/stm/common-hal/audioio/AudioOut.c index e5dd620521c..d0c96d120e7 100644 --- a/ports/stm/common-hal/audioio/AudioOut.c +++ b/ports/stm/common-hal/audioio/AudioOut.c @@ -746,37 +746,3 @@ bool common_hal_audioio_audioout_get_paused(audioio_audioout_obj_t *self) { // quiescent after stop. Worth aligning to the espressif/stm convention. return self->playing && self->paused; } - -// --------------------------------------------------------------------------- -// Reset hook -// --------------------------------------------------------------------------- - -void audioout_reset(void) { - if (active_audioout != NULL) { - // Emergency stop: halt timer and DMA without ramping. - TIM6->CR1 &= ~TIM_CR1_CEN; - HAL_DAC_Stop_DMA(&handle, DAC_CHANNEL_1); - HAL_NVIC_DisableIRQ(DMA1_Stream5_IRQn); - if (active_audioout->dma_buffer_r) { - HAL_DAC_Stop_DMA(&handle, DAC_CHANNEL_2); - HAL_NVIC_DisableIRQ(DMA1_Stream6_IRQn); - m_free(active_audioout->dma_buffer_r); - active_audioout->dma_buffer_r = NULL; - } - if (active_audioout->dma_buffer) { - m_free(active_audioout->dma_buffer); - active_audioout->dma_buffer = NULL; - } - active_audioout->sample = MP_OBJ_NULL; - active_audioout->stopping = false; - active_audioout->paused = false; - active_audioout->playing = false; - // Mark the object deinited and drop both pin references so the next - // construct() starts from a fully clean state. reset_all_pins (run - // elsewhere in reset_port) releases the actual pin claims. - active_audioout->left_channel = NULL; - active_audioout->right_channel = NULL; - active_audioout = NULL; - } - __HAL_RCC_TIM6_CLK_DISABLE(); -} diff --git a/ports/stm/common-hal/audioio/AudioOut.h b/ports/stm/common-hal/audioio/AudioOut.h index 340118819a9..a519d5f3869 100644 --- a/ports/stm/common-hal/audioio/AudioOut.h +++ b/ports/stm/common-hal/audioio/AudioOut.h @@ -69,4 +69,3 @@ typedef struct { } audioio_audioout_obj_t; // Called from reset_port() to stop any active playback on soft-reset. -void audioout_reset(void); diff --git a/ports/stm/common-hal/busio/I2C.c b/ports/stm/common-hal/busio/I2C.c index 7f7eb990e4d..f2fe734c72a 100644 --- a/ports/stm/common-hal/busio/I2C.c +++ b/ports/stm/common-hal/busio/I2C.c @@ -152,10 +152,6 @@ void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, HAL_NVIC_EnableIRQ(self->irq); } -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - never_reset_pin_number(self->scl->pin->port, self->scl->pin->number); - never_reset_pin_number(self->sda->pin->port, self->sda->pin->number); -} bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { return self->sda == NULL; diff --git a/ports/stm/common-hal/busio/SPI.c b/ports/stm/common-hal/busio/SPI.c index 24b65fbf54c..ecc58f7998b 100644 --- a/ports/stm/common-hal/busio/SPI.c +++ b/ports/stm/common-hal/busio/SPI.c @@ -213,16 +213,6 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, } } -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { - - never_reset_pin_number(self->sck->pin->port, self->sck->pin->number); - if (self->mosi != NULL) { - never_reset_pin_number(self->mosi->pin->port, self->mosi->pin->number); - } - if (self->miso != NULL) { - never_reset_pin_number(self->miso->pin->port, self->miso->pin->number); - } -} bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { return self->sck == NULL; diff --git a/ports/stm/common-hal/busio/UART.c b/ports/stm/common-hal/busio/UART.c index e44fbc66a6c..2d80ca15215 100644 --- a/ports/stm/common-hal/busio/UART.c +++ b/ports/stm/common-hal/busio/UART.c @@ -20,7 +20,6 @@ // arrays use 0 based numbering: UART1 is stored at index 0 static bool reserved_uart[MAX_UART]; -static bool never_reset_uart[MAX_UART]; int errflag; // Used to restart read halts static void uart_clock_enable(uint16_t mask); @@ -42,18 +41,6 @@ static USART_TypeDef *assign_uart_or_throw(busio_uart_obj_t *self, bool pin_eval } } -void uart_reset(void) { - uint16_t never_reset_mask = 0x00; - for (uint8_t i = 0; i < MAX_UART; i++) { - if (!never_reset_uart[i]) { - reserved_uart[i] = false; - MP_STATE_PORT(cpy_uart_obj_all)[i] = NULL; - } else { - never_reset_mask |= 1 << i; - } - } - uart_clock_disable(ALL_UARTS & ~(never_reset_mask)); -} void common_hal_busio_uart_construct(busio_uart_obj_t *self, const mcu_pin_obj_t *tx, const mcu_pin_obj_t *rx, @@ -224,16 +211,6 @@ void common_hal_busio_uart_construct(busio_uart_obj_t *self, errflag = HAL_OK; } -void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { - for (size_t i = 0; i < MP_ARRAY_SIZE(mcu_uart_banks); i++) { - if (mcu_uart_banks[i] == self->handle.Instance) { - never_reset_uart[i] = true; - never_reset_pin_number(self->tx->pin->port, self->tx->pin->number); - never_reset_pin_number(self->rx->pin->port, self->rx->pin->number); - break; - } - } -} bool common_hal_busio_uart_deinited(busio_uart_obj_t *self) { return self->tx == NULL && self->rx == NULL; @@ -247,7 +224,9 @@ void common_hal_busio_uart_deinit(busio_uart_obj_t *self) { for (size_t i = 0; i < MP_ARRAY_SIZE(mcu_uart_banks); i++) { if (mcu_uart_banks[i] == self->handle.Instance) { reserved_uart[i] = false; - never_reset_uart[i] = false; + // Reset the peripheral and turn its clock back off, matching construct()'s + // uart_clock_enable() for this bank. + uart_clock_disable(1 << i); break; } } @@ -602,6 +581,7 @@ static void uart_clock_disable(uint16_t mask) { #endif } + static void uart_assign_irq(busio_uart_obj_t *self, USART_TypeDef *USARTx) { #ifdef USART1 if (USARTx == USART1) { diff --git a/ports/stm/common-hal/busio/UART.h b/ports/stm/common-hal/busio/UART.h index 5df7d457488..c047dbf58d0 100644 --- a/ports/stm/common-hal/busio/UART.h +++ b/ports/stm/common-hal/busio/UART.h @@ -34,5 +34,3 @@ typedef struct { bool sigint_enabled; } busio_uart_obj_t; - -void uart_reset(void); diff --git a/ports/stm/common-hal/canio/CAN.c b/ports/stm/common-hal/canio/CAN.c index 72a74a4b138..6bde0f327c8 100644 --- a/ports/stm/common-hal/canio/CAN.c +++ b/ports/stm/common-hal/canio/CAN.c @@ -261,10 +261,3 @@ void common_hal_canio_can_deinit(canio_can_obj_t *self) { } self->handle.Instance = NULL; } - -void common_hal_canio_reset(void) { - for (size_t i = 0; i < MP_ARRAY_SIZE(mcu_can_banks); i++) { - SET_BIT(mcu_can_banks[i]->MCR, CAN_MCR_RESET); - reserved_can[i] = 0; - } -} diff --git a/ports/stm/common-hal/digitalio/DigitalInOut.c b/ports/stm/common-hal/digitalio/DigitalInOut.c index 359e278cb02..989052ad940 100644 --- a/ports/stm/common-hal/digitalio/DigitalInOut.c +++ b/ports/stm/common-hal/digitalio/DigitalInOut.c @@ -21,10 +21,6 @@ #error unknown MCU for DigitalInOut #endif -void common_hal_digitalio_digitalinout_never_reset( - digitalio_digitalinout_obj_t *self) { - never_reset_pin_number(self->pin->port, self->pin->number); -} digitalinout_result_t common_hal_digitalio_digitalinout_construct( digitalio_digitalinout_obj_t *self, const mcu_pin_obj_t *pin) { diff --git a/ports/stm/common-hal/microcontroller/Pin.c b/ports/stm/common-hal/microcontroller/Pin.c index bdf5e5fb16c..a1b045ebce6 100644 --- a/ports/stm/common-hal/microcontroller/Pin.c +++ b/ports/stm/common-hal/microcontroller/Pin.c @@ -31,17 +31,6 @@ GPIO_TypeDef *ports[] = {GPIOA, GPIOB, GPIOC}; #define GPIO_PORT_COUNT (MP_ARRAY_SIZE(ports)) static uint16_t claimed_pins[GPIO_PORT_COUNT]; -static uint16_t __ALIGNED(4) never_reset_pins[GPIO_PORT_COUNT]; - -void reset_all_pins(void) { - // Reset claimed pins - for (uint8_t i = 0; i < GPIO_PORT_COUNT; i++) { - claimed_pins[i] = never_reset_pins[i]; - } - for (uint8_t i = 0; i < GPIO_PORT_COUNT; i++) { - HAL_GPIO_DeInit(ports[i], ~never_reset_pins[i]); - } -} // Mark pin as free and return it to a quiescent state. void reset_pin_number(uint8_t pin_port, uint8_t pin_number) { @@ -54,22 +43,9 @@ void reset_pin_number(uint8_t pin_port, uint8_t pin_number) { } // Clear claimed bit & reset claimed_pins[pin_port] &= ~(1 << pin_number); - never_reset_pins[pin_port] &= ~(1 << pin_number); HAL_GPIO_DeInit(ports[pin_port], 1 << pin_number); } -void never_reset_pin_number(uint8_t pin_port, uint8_t pin_number) { - if (pin_number == NO_PIN) { - return; - } - never_reset_pins[pin_port] |= 1 << pin_number; - // Make sure never reset pins are also always claimed - claimed_pins[pin_port] |= 1 << pin_number; -} - -void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - never_reset_pin_number(pin->port, pin->number); -} void common_hal_reset_pin(const mcu_pin_obj_t *pin) { if (pin == NULL) { diff --git a/ports/stm/common-hal/microcontroller/Pin.h b/ports/stm/common-hal/microcontroller/Pin.h index a1d347f26da..f1b54edc81f 100644 --- a/ports/stm/common-hal/microcontroller/Pin.h +++ b/ports/stm/common-hal/microcontroller/Pin.h @@ -10,12 +10,14 @@ #include "peripherals/pins.h" -void reset_all_pins(void); +void common_hal_reset_pin(const mcu_pin_obj_t *pin); // reset_pin_number takes the pin number instead of the pointer so that objects don't // need to store a full pointer. void reset_pin_number(uint8_t pin_port, uint8_t pin_number); void claim_pin(uint8_t pin_port, uint8_t pin_number); bool pin_number_is_free(uint8_t pin_port, uint8_t pin_number); -void never_reset_pin_number(uint8_t pin_port, uint8_t pin_number); GPIO_TypeDef *pin_port(uint8_t pin_port); uint16_t pin_mask(uint8_t pin_number); + +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); +void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin); diff --git a/ports/stm/common-hal/pulseio/PulseIn.c b/ports/stm/common-hal/pulseio/PulseIn.c index 86d304af5f1..5320da8b76a 100644 --- a/ports/stm/common-hal/pulseio/PulseIn.c +++ b/ports/stm/common-hal/pulseio/PulseIn.c @@ -158,7 +158,8 @@ void common_hal_pulseio_pulsein_deinit(pulseio_pulsein_obj_t *self) { } // Remove pulsein slot from shared array callback_obj_ref[self->pin->number] = NULL; - stm_peripherals_exti_free(self->pin->number); + // Disable the EXTI IRQ, unreserve the line and clear the callback. + stm_peripherals_exti_reset_exti(self->pin->number); reset_pin_number(self->pin->port, self->pin->number); self->pin = NULL; diff --git a/ports/stm/common-hal/pwmio/PWMOut.c b/ports/stm/common-hal/pwmio/PWMOut.c index fe516065fbd..2c84d9d2dad 100644 --- a/ports/stm/common-hal/pwmio/PWMOut.c +++ b/ports/stm/common-hal/pwmio/PWMOut.c @@ -167,9 +167,6 @@ pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self, return PWMOUT_OK; } -void common_hal_pwmio_pwmout_never_reset(pwmio_pwmout_obj_t *self) { - common_hal_never_reset_pin(self->pin); -} bool common_hal_pwmio_pwmout_deinited(pwmio_pwmout_obj_t *self) { return self->tim == NULL; diff --git a/ports/stm/common-hal/rgbmatrix/RGBMatrix.c b/ports/stm/common-hal/rgbmatrix/RGBMatrix.c index a8014222d7d..913953e49ef 100644 --- a/ports/stm/common-hal/rgbmatrix/RGBMatrix.c +++ b/ports/stm/common-hal/rgbmatrix/RGBMatrix.c @@ -16,7 +16,6 @@ extern void _PM_IRQ_HANDLER(void); void *common_hal_rgbmatrix_timer_allocate(rgbmatrix_rgbmatrix_obj_t *self) { TIM_TypeDef *timer = stm_peripherals_find_timer(); stm_peripherals_timer_reserve(timer); - stm_peripherals_timer_never_reset(timer); return timer; } diff --git a/ports/stm/common-hal/rtc/RTC.h b/ports/stm/common-hal/rtc/RTC.h index b6e709bed4f..78492ded426 100644 --- a/ports/stm/common-hal/rtc/RTC.h +++ b/ports/stm/common-hal/rtc/RTC.h @@ -7,4 +7,3 @@ #pragma once extern void rtc_init(void); -extern void rtc_reset(void); diff --git a/ports/stm/common-hal/sdioio/SDCard.c b/ports/stm/common-hal/sdioio/SDCard.c index ef708248716..9843dc76ede 100644 --- a/ports/stm/common-hal/sdioio/SDCard.c +++ b/ports/stm/common-hal/sdioio/SDCard.c @@ -18,7 +18,6 @@ #include "shared-bindings/microcontroller/Pin.h" static bool reserved_sdio[MP_ARRAY_SIZE(mcu_sdio_banks)]; -static bool never_reset_sdio[MP_ARRAY_SIZE(mcu_sdio_banks)]; static const mcu_periph_obj_t *find_pin_function(const mcu_periph_obj_t *table, size_t sz, const mcu_pin_obj_t *pin, int periph_index) { for (size_t i = 0; i < sz; i++, table++) { @@ -334,12 +333,6 @@ bool common_hal_sdioio_sdcard_deinited(sdioio_sdcard_obj_t *self) { return self->command == NULL; } -static void never_reset_mcu_periph(const mcu_periph_obj_t *periph) { - if (periph) { - never_reset_pin_number(periph->pin->port, periph->pin->number); - } -} - static void reset_mcu_periph(const mcu_periph_obj_t *periph) { if (periph) { reset_pin_number(periph->pin->port, periph->pin->number); @@ -352,43 +345,15 @@ void common_hal_sdioio_sdcard_deinit(sdioio_sdcard_obj_t *self) { } reserved_sdio[self->command->periph_index - 1] = false; - never_reset_sdio[self->command->periph_index - 1] = false; reset_mcu_periph(self->command); self->command = NULL; reset_mcu_periph(self->clock); - self->command = NULL; + self->clock = NULL; for (size_t i = 0; i < MP_ARRAY_SIZE(self->data); i++) { reset_mcu_periph(self->data[i]); self->data[i] = NULL; } } - -void common_hal_sdioio_sdcard_never_reset(sdioio_sdcard_obj_t *self) { - if (common_hal_sdioio_sdcard_deinited(self)) { - return; - } - - if (never_reset_sdio[self->command->periph_index] - 1) { - return; - } - - never_reset_sdio[self->command->periph_index - 1] = true; - - never_reset_mcu_periph(self->command); - never_reset_mcu_periph(self->clock); - - for (size_t i = 0; i < MP_ARRAY_SIZE(self->data); i++) { - never_reset_mcu_periph(self->data[i]); - } -} - -void sdioio_reset(void) { - for (size_t i = 0; i < MP_ARRAY_SIZE(reserved_sdio); i++) { - if (!never_reset_sdio[i]) { - reserved_sdio[i] = false; - } - } -} diff --git a/ports/stm/common-hal/sdioio/SDCard.h b/ports/stm/common-hal/sdioio/SDCard.h index 8f4d9453033..0cd79a3accf 100644 --- a/ports/stm/common-hal/sdioio/SDCard.h +++ b/ports/stm/common-hal/sdioio/SDCard.h @@ -23,5 +23,3 @@ typedef struct { uint32_t frequency; uint32_t capacity; } sdioio_sdcard_obj_t; - -void sdioio_reset(void); diff --git a/ports/stm/mpconfigport.mk b/ports/stm/mpconfigport.mk index bd2b2238eef..aa5038964c5 100644 --- a/ports/stm/mpconfigport.mk +++ b/ports/stm/mpconfigport.mk @@ -1,3 +1,6 @@ +# Properties get a dedicated linker section here, so they can drop the unused slots. +CIRCUITPY_OPTIMIZE_PROPERTY_FLASH_SIZE ?= 1 + LONGINT_IMPL ?= MPZ INTERNAL_LIBM ?= 1 @@ -21,6 +24,7 @@ ifeq ($(MCU_VARIANT),STM32F407xx) endif ifeq ($(MCU_SERIES),F4) + CIRCUITPY_LOAD_NATIVE ?= 1 # Audio via PWM (F405/F407 also supports DAC-based audioio; set above) CIRCUITPY_AUDIOIO ?= 0 CIRCUITPY_AUDIOCORE ?= 1 @@ -103,3 +107,6 @@ endif CIRCUITPY_PARALLELDISPLAYBUS := 0 CIRCUITPY_BUILD_EXTENSIONS ?= bin + +# Cleanup is performed by GC finalizers (__del__ -> deinit) instead of a bulk pin reset. +CIRCUITPY_BULK_RESET = 0 diff --git a/ports/stm/peripherals/exti.c b/ports/stm/peripherals/exti.c index 19fa3079ba0..8448c74a9d7 100644 --- a/ports/stm/peripherals/exti.c +++ b/ports/stm/peripherals/exti.c @@ -13,26 +13,10 @@ #if !(CPY_STM32H7) static bool stm_exti_reserved[STM32_GPIO_PORT_SIZE]; -static bool stm_exti_never_reset[STM32_GPIO_PORT_SIZE]; static void (*stm_exti_callback[STM32_GPIO_PORT_SIZE])(uint8_t num); -void exti_reset(void) { - for (size_t i = 0; i < STM32_GPIO_PORT_SIZE; i++) { - if (!stm_exti_never_reset[i]) { - stm_exti_reserved[i] = false; - stm_exti_callback[i] = NULL; - stm_peripherals_exti_disable(i); - } - } -} - -void stm_peripherals_exti_never_reset(uint8_t num) { - stm_exti_never_reset[num] = true; -} - void stm_peripherals_exti_reset_exti(uint8_t num) { stm_peripherals_exti_disable(num); - stm_exti_never_reset[num] = false; stm_exti_reserved[num] = false; stm_exti_callback[num] = NULL; } @@ -61,10 +45,6 @@ void stm_peripherals_exti_set_callback(void (*callback)(uint8_t num), uint8_t nu stm_exti_callback[number] = callback; } -void stm_peripherals_exti_free(uint8_t num) { - stm_exti_reserved[num] = true; -} - IRQn_Type stm_peripherals_exti_get_irq(uint8_t num) { if (num == 0) { return EXTI0_IRQn; diff --git a/ports/stm/peripherals/exti.h b/ports/stm/peripherals/exti.h index 94b62f1768e..0d53eb8c7d7 100644 --- a/ports/stm/peripherals/exti.h +++ b/ports/stm/peripherals/exti.h @@ -10,13 +10,10 @@ #define STM32_GPIO_PORT_SIZE 16 -void exti_reset(void); -void stm_peripherals_exti_never_reset(uint8_t num); void stm_peripherals_exti_reset_exti(uint8_t num); bool stm_peripherals_exti_is_free(uint8_t num); bool stm_peripherals_exti_reserve(uint8_t num); void stm_peripherals_exti_enable(uint8_t num); void stm_peripherals_exti_disable(uint8_t num); void stm_peripherals_exti_set_callback(void (*callback)(uint8_t num), uint8_t number); -void stm_peripherals_exti_free(uint8_t num); IRQn_Type stm_peripherals_exti_get_irq(uint8_t num); diff --git a/ports/stm/peripherals/sdram.c b/ports/stm/peripherals/sdram.c index 6179197fa95..31ffe780035 100644 --- a/ports/stm/peripherals/sdram.c +++ b/ports/stm/peripherals/sdram.c @@ -71,7 +71,6 @@ void sdram_init(const struct stm32_sdram_config *config) { GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF12_FMC; HAL_GPIO_Init(pin_port(sdram_pin_list[i].pin->port), &GPIO_InitStruct); - never_reset_pin_number(sdram_pin_list[i].pin->port, sdram_pin_list[i].pin->number); } } diff --git a/ports/stm/peripherals/stm32f4/stm32f401xe/gpio.c b/ports/stm/peripherals/stm32f4/stm32f401xe/gpio.c index 7e1809175f5..1a5267d510b 100644 --- a/ports/stm/peripherals/stm32f4/stm32f401xe/gpio.c +++ b/ports/stm/peripherals/stm32f4/stm32f401xe/gpio.c @@ -16,9 +16,9 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK } diff --git a/ports/stm/peripherals/stm32f4/stm32f405xx/gpio.c b/ports/stm/peripherals/stm32f4/stm32f405xx/gpio.c index 9a1e8eac52c..1274763c53d 100644 --- a/ports/stm/peripherals/stm32f4/stm32f405xx/gpio.c +++ b/ports/stm/peripherals/stm32f4/stm32f405xx/gpio.c @@ -17,19 +17,14 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 13); // PC13 anti tamp - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK - // never_reset_pin_number(0,15); //PA15 JTDI - // never_reset_pin_number(1,3); //PB3 JTDO - // never_reset_pin_number(1,4); //PB4 JTRST + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 13); // PC13 anti tamp + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK // Port H is not included in GPIO port array - // never_reset_pin_number(5,0); //PH0 JTDO - // never_reset_pin_number(5,1); //PH1 JTRST } void stm32f4_peripherals_status_led(uint8_t led, uint8_t state) { diff --git a/ports/stm/peripherals/stm32f4/stm32f407xx/gpio.c b/ports/stm/peripherals/stm32f4/stm32f407xx/gpio.c index 9a1e8eac52c..1274763c53d 100644 --- a/ports/stm/peripherals/stm32f4/stm32f407xx/gpio.c +++ b/ports/stm/peripherals/stm32f4/stm32f407xx/gpio.c @@ -17,19 +17,14 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 13); // PC13 anti tamp - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK - // never_reset_pin_number(0,15); //PA15 JTDI - // never_reset_pin_number(1,3); //PB3 JTDO - // never_reset_pin_number(1,4); //PB4 JTRST + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 13); // PC13 anti tamp + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK // Port H is not included in GPIO port array - // never_reset_pin_number(5,0); //PH0 JTDO - // never_reset_pin_number(5,1); //PH1 JTRST } void stm32f4_peripherals_status_led(uint8_t led, uint8_t state) { diff --git a/ports/stm/peripherals/stm32f4/stm32f411xe/gpio.c b/ports/stm/peripherals/stm32f4/stm32f411xe/gpio.c index db53087fd24..5e5543d7370 100644 --- a/ports/stm/peripherals/stm32f4/stm32f411xe/gpio.c +++ b/ports/stm/peripherals/stm32f4/stm32f411xe/gpio.c @@ -17,20 +17,18 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); - // Never reset pins + // Pins in use by the system; mark them claimed so user code can't use them. // TODO: Move this out of peripherals. These helpers shouldn't reference anything CircuitPython // specific. - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT #if !(BOARD_OVERWRITE_SWD) - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK #endif // Port H is not included in GPIO port array - // never_reset_pin_number(5,0); //PH0 JTDO - // never_reset_pin_number(5,1); //PH1 JTRST } // LEDs are inverted on F411 DISCO diff --git a/ports/stm/peripherals/stm32f4/stm32f412cx/gpio.c b/ports/stm/peripherals/stm32f4/stm32f412cx/gpio.c index 4cca79bf9e7..b6d45cef959 100644 --- a/ports/stm/peripherals/stm32f4/stm32f412cx/gpio.c +++ b/ports/stm/peripherals/stm32f4/stm32f412cx/gpio.c @@ -13,7 +13,7 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK } diff --git a/ports/stm/peripherals/stm32f4/stm32f412zx/gpio.c b/ports/stm/peripherals/stm32f4/stm32f412zx/gpio.c index 94c2faf68bf..a65ac6f4378 100644 --- a/ports/stm/peripherals/stm32f4/stm32f412zx/gpio.c +++ b/ports/stm/peripherals/stm32f4/stm32f412zx/gpio.c @@ -18,17 +18,12 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOG_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 13); // PC13 anti tamp - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK - // never_reset_pin_number(0,15); //PA15 JTDI - // never_reset_pin_number(1,3); //PB3 JTDO - // never_reset_pin_number(1,4); //PB4 JTRST + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 13); // PC13 anti tamp + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK // Port H is not included in GPIO port array - // never_reset_pin_number(5,0); //PH0 JTDO - // never_reset_pin_number(5,1); //PH1 JTRST } diff --git a/ports/stm/peripherals/stm32f4/stm32f446xx/gpio.c b/ports/stm/peripherals/stm32f4/stm32f446xx/gpio.c index 25a7ea5a6b8..c5050b85566 100644 --- a/ports/stm/peripherals/stm32f4/stm32f446xx/gpio.c +++ b/ports/stm/peripherals/stm32f4/stm32f446xx/gpio.c @@ -15,12 +15,12 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 13); // PC13 anti tamp - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 13); // PC13 anti tamp + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK } void stm32f4_peripherals_status_led(uint8_t led, uint8_t state) { diff --git a/ports/stm/peripherals/stm32f7/stm32f746xx/gpio.c b/ports/stm/peripherals/stm32f7/stm32f746xx/gpio.c index 96cfd8eaf85..98e9cceaae1 100644 --- a/ports/stm/peripherals/stm32f7/stm32f746xx/gpio.c +++ b/ports/stm/peripherals/stm32f7/stm32f746xx/gpio.c @@ -22,11 +22,14 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOJ_CLK_ENABLE(); __HAL_RCC_GPIOK_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK - never_reset_pin_number(7, 0); // PH0 OSC_IN - never_reset_pin_number(7, 1); // PH1 OSC_OUT + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK + // Only guard the port-H oscillator pins when the package has port H in the pin array. + #if defined(TFBGA216) + claim_pin(7, 0); // PH0 OSC_IN + claim_pin(7, 1); // PH1 OSC_OUT + #endif } diff --git a/ports/stm/peripherals/stm32f7/stm32f767xx/gpio.c b/ports/stm/peripherals/stm32f7/stm32f767xx/gpio.c index a9fecc33540..6e495a9b80d 100644 --- a/ports/stm/peripherals/stm32f7/stm32f767xx/gpio.c +++ b/ports/stm/peripherals/stm32f7/stm32f767xx/gpio.c @@ -18,9 +18,9 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOG_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK } diff --git a/ports/stm/peripherals/stm32h7/stm32h743xx/gpio.c b/ports/stm/peripherals/stm32h7/stm32h743xx/gpio.c index a9fecc33540..6e495a9b80d 100644 --- a/ports/stm/peripherals/stm32h7/stm32h743xx/gpio.c +++ b/ports/stm/peripherals/stm32h7/stm32h743xx/gpio.c @@ -18,9 +18,9 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOG_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK } diff --git a/ports/stm/peripherals/stm32h7/stm32h750xx/gpio.c b/ports/stm/peripherals/stm32h7/stm32h750xx/gpio.c index f5ad843c9a6..876dcecfc9f 100644 --- a/ports/stm/peripherals/stm32h7/stm32h750xx/gpio.c +++ b/ports/stm/peripherals/stm32h7/stm32h750xx/gpio.c @@ -19,17 +19,16 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOI_CLK_ENABLE(); -// Never reset pins - never_reset_pin_number(7, 0); // PH00 OSC32_IN - never_reset_pin_number(7, 1); // PH01 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(7, 0); // PH00 OSC32_IN + claim_pin(7, 1); // PH01 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK // qspi flash pins for the Daisy Seed -- TODO ? - never_reset_pin_number(5, 6); // PF06 QSPI IO3 - never_reset_pin_number(5, 7); // PF07 QSPI IO2 - never_reset_pin_number(5, 8); // PF08 QSPI IO0 - never_reset_pin_number(5, 9); // PF09 QSPI IO1 - never_reset_pin_number(5, 10); // PF10 QSPI CLK - never_reset_pin_number(6, 6); // PG06 QSPI NCS - + claim_pin(5, 6); // PF06 QSPI IO3 + claim_pin(5, 7); // PF07 QSPI IO2 + claim_pin(5, 8); // PF08 QSPI IO0 + claim_pin(5, 9); // PF09 QSPI IO1 + claim_pin(5, 10); // PF10 QSPI CLK + claim_pin(6, 6); // PG06 QSPI NCS } diff --git a/ports/stm/peripherals/stm32l4/stm32l433xx/gpio.c b/ports/stm/peripherals/stm32l4/stm32l433xx/gpio.c index eb8914b5585..c2967752b93 100644 --- a/ports/stm/peripherals/stm32l4/stm32l433xx/gpio.c +++ b/ports/stm/peripherals/stm32l4/stm32l433xx/gpio.c @@ -14,11 +14,11 @@ void stm32_peripherals_gpio_init(void) { __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK } void stm32l4_peripherals_status_led(uint8_t led, uint8_t state) { diff --git a/ports/stm/peripherals/stm32l4/stm32l4r5xx/gpio.c b/ports/stm/peripherals/stm32l4/stm32l4r5xx/gpio.c index a4adc58e42a..342a071213d 100644 --- a/ports/stm/peripherals/stm32l4/stm32l4r5xx/gpio.c +++ b/ports/stm/peripherals/stm32l4/stm32l4r5xx/gpio.c @@ -21,18 +21,13 @@ void stm32_peripherals_gpio_init(void) { // __HAL_RCC_GPIOH_CLK_ENABLE(); // __HAL_RCC_GPIOI_CLK_ENABLE(); - // Never reset pins - never_reset_pin_number(2, 14); // PC14 OSC32_IN - never_reset_pin_number(2, 15); // PC15 OSC32_OUT - never_reset_pin_number(0, 13); // PA13 SWDIO - never_reset_pin_number(0, 14); // PA14 SWCLK - // never_reset_pin_number(0,15); //PA15 JTDI - // never_reset_pin_number(1,3); //PB3 JTDO - // never_reset_pin_number(1,4); //PB4 JTRST + // Pins in use by the system; mark them claimed so user code can't use them. + claim_pin(2, 14); // PC14 OSC32_IN + claim_pin(2, 15); // PC15 OSC32_OUT + claim_pin(0, 13); // PA13 SWDIO + claim_pin(0, 14); // PA14 SWCLK // Port H is not included in GPIO port array - // never_reset_pin_number(5,0); //PH0 JTDO - // never_reset_pin_number(5,1); //PH1 JTRST } void stm32l4_peripherals_status_led(uint8_t led, uint8_t state) { diff --git a/ports/stm/peripherals/timers.c b/ports/stm/peripherals/timers.c index 25cb9efcde0..4ded6c2637a 100644 --- a/ports/stm/peripherals/timers.c +++ b/ports/stm/peripherals/timers.c @@ -20,7 +20,6 @@ #define NULL_IRQ 0xFF static bool stm_timer_reserved[MP_ARRAY_SIZE(mcu_tim_banks)]; -static bool stm_timer_never_reset[MP_ARRAY_SIZE(mcu_tim_banks)]; typedef void (*stm_timer_callback_t)(void); // Array of function pointers. @@ -268,22 +267,6 @@ void stm_peripherals_timer_free(TIM_TypeDef *instance) { stm_timer_callback[tim_idx] = NULL; tim_clock_disable(1 << tim_idx); stm_timer_reserved[tim_idx] = false; - stm_timer_never_reset[tim_idx] = false; -} - -void stm_peripherals_timer_never_reset(TIM_TypeDef *instance) { - size_t tim_idx = stm_peripherals_timer_get_index(instance); - stm_timer_never_reset[tim_idx] = true; -} - -void stm_peripherals_timer_reset_ok(TIM_TypeDef *instance) { - size_t tim_idx = stm_peripherals_timer_get_index(instance); - stm_timer_never_reset[tim_idx] = false; -} - -bool stm_peripherals_timer_is_never_reset(TIM_TypeDef *instance) { - size_t tim_idx = stm_peripherals_timer_get_index(instance); - return stm_timer_never_reset[tim_idx]; } bool stm_peripherals_timer_is_reserved(TIM_TypeDef *instance) { diff --git a/ports/stm/peripherals/timers.h b/ports/stm/peripherals/timers.h index 00c198250cb..65070886b57 100644 --- a/ports/stm/peripherals/timers.h +++ b/ports/stm/peripherals/timers.h @@ -20,8 +20,5 @@ TIM_TypeDef *stm_peripherals_find_timer(void); void stm_peripherals_timer_preinit(TIM_TypeDef *instance, uint8_t prio, void (*callback)(void)); void stm_peripherals_timer_reserve(TIM_TypeDef *instance); void stm_peripherals_timer_free(TIM_TypeDef *instance); -void stm_peripherals_timer_never_reset(TIM_TypeDef *instance); -void stm_peripherals_timer_reset_ok(TIM_TypeDef *instance); -bool stm_peripherals_timer_is_never_reset(TIM_TypeDef *instance); bool stm_peripherals_timer_is_reserved(TIM_TypeDef *instance); size_t stm_peripherals_timer_get_index(TIM_TypeDef *instance); diff --git a/ports/stm/supervisor/port.c b/ports/stm/supervisor/port.c index 21da8b2d653..6e86e19caf0 100644 --- a/ports/stm/supervisor/port.c +++ b/ports/stm/supervisor/port.c @@ -396,19 +396,6 @@ void reset_port(void) { #if CIRCUITPY_RTC rtc_reset(); #endif - - #if CIRCUITPY_BUSIO - uart_reset(); - #endif - #if CIRCUITPY_SDIOIO - sdioio_reset(); - #endif - #if CIRCUITPY_ALARM - exti_reset(); - #endif - #if CIRCUITPY_AUDIOIO - audioout_reset(); - #endif } void reset_to_bootloader(void) { diff --git a/ports/stm/supervisor/usb.c b/ports/stm/supervisor/usb.c index 7c782f61d0d..c1387378912 100644 --- a/ports/stm/supervisor/usb.c +++ b/ports/stm/supervisor/usb.c @@ -78,8 +78,6 @@ void init_usb_hardware(void) { #endif HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - never_reset_pin_number(0, 11); - never_reset_pin_number(0, 12); claim_pin(0, 11); claim_pin(0, 12); @@ -89,7 +87,6 @@ void init_usb_hardware(void) { GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - never_reset_pin_number(0, 9); claim_pin(0, 9); #endif @@ -105,7 +102,6 @@ void init_usb_hardware(void) { GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS; #endif HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - never_reset_pin_number(0, 10); claim_pin(0, 10); #endif @@ -115,7 +111,6 @@ void init_usb_hardware(void) { GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); - never_reset_pin_number(0, 8); claim_pin(0, 8); #endif diff --git a/ports/unix/Makefile b/ports/unix/Makefile index def45e2a405..62fa4c732e9 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -17,6 +17,10 @@ endif # If the build directory is not given, make it reflect the variant name. BUILD ?= build-$(VARIANT) +ifneq ($(MICROPY_FORCE_32BIT),) +$(warning *** The MICROPY_FORCE_32BIT flag is no longer affecting builds, please update your environment ***) +endif + include ../../py/mkenv.mk -include mpconfigport.mk include $(VARIANT_DIR)/mpconfigvariant.mk @@ -62,21 +66,25 @@ CFLAGS += -D_FILE_OFFSET_BITS=64 # This option has no effect on 64-bit builds. CFLAGS += -D_TIME_BITS=64 +# Debug info flag is added if either DEBUG=1 or STRIP= is passed to make +# (to produce a debug binary, or a release binary with all symbols, respectively.) +DEBUG_INFO = -ggdb3 + # Debugging/Optimization ifdef DEBUG COPT ?= -Og +CFLAGS += $(DEBUG_INFO) else COPT ?= -Os COPT += -DNDEBUG +ifndef STRIP +CFLAGS += $(DEBUG_INFO) endif +endif # ifdef DEBUG # Remove unused sections. COPT += -fdata-sections -ffunction-sections -# Note: Symbols and debug information will still be stripped from the final binary -# unless "DEBUG=1" or "STRIP=" is passed to make, see README.md for details. -CFLAGS += -g - ifndef DEBUG # _FORTIFY_SOURCE is a feature in gcc/glibc which is intended to provide extra # security for detecting buffer overflows. Some distros (Ubuntu at the very least) @@ -110,11 +118,7 @@ endif # while cross-compile ports require gcc, so we test here for OSX and # if necessary override the value of 'CC' set in py/mkenv.mk ifeq ($(UNAME_S),Darwin) -ifeq ($(MICROPY_FORCE_32BIT),1) -CC = clang -m32 -else CC = clang -endif # Use clang syntax for map file LDFLAGS_ARCH = -Wl,-map,$@.map -Wl,-dead_strip else @@ -126,16 +130,6 @@ LDFLAGS += $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) # Flags to link with pthread library LIBPTHREAD = -lpthread -ifeq ($(MICROPY_FORCE_32BIT),1) -# Note: you may need to install i386 versions of dependency packages, -# starting with linux-libc-dev:i386 -ifeq ($(MICROPY_PY_FFI),1) -ifeq ($(UNAME_S),Linux) -CFLAGS += -I/usr/include/i686-linux-gnu -endif -endif -endif - ifeq ($(MICROPY_USE_READLINE),1) INC += -I$(TOP)/shared/readline CFLAGS += -DMICROPY_USE_READLINE=1 @@ -187,11 +181,7 @@ ifeq ($(MICROPY_STANDALONE),1) GIT_SUBMODULES += lib/libffi DEPLIBS += libffi LIBFFI_CFLAGS := -I$(shell ls -1d $(BUILD)/lib/libffi/include) - ifeq ($(MICROPY_FORCE_32BIT),1) - LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib32/libffi.a - else - LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib/libffi.a - endif +LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib/libffi.a else # Use system version of libffi. LIBFFI_CFLAGS := $(shell pkg-config --cflags libffi) @@ -263,13 +253,6 @@ CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs CFLAGS += -DMICROPY_MODULE_FROZEN_STR endif -# CIRCUITPY-CHANGE: use gnu11 instead of gnu99 -CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu11,$(CFLAGS) $(CXXFLAGS_MOD)) - -ifeq ($(MICROPY_FORCE_32BIT),1) -RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-march=x86' -endif - ifeq ($(CROSS_COMPILE),arm-linux-gnueabi-) # Force disable error text compression when compiling for ARM as the compiler # cannot optimise out the giant strcmp list generated for MP_MATCH_COMPRESSED. diff --git a/ports/unix/README.md b/ports/unix/README.md index ee983a882cc..720be8a047c 100644 --- a/ports/unix/README.md +++ b/ports/unix/README.md @@ -5,9 +5,10 @@ The "unix" port runs in standard Unix-like environments including Linux, BSD, macOS, and Windows Subsystem for Linux. The x86 and x64 architectures are supported (i.e. x86 32- and 64-bit), as well -as ARM and MIPS. Extending the unix port to another architecture requires -writing some assembly code for the exception handling and garbage collection. -Alternatively, a fallback implementation based on setjmp/longjmp can be used. +as ARM, MIPS, RISC-V, and LoongArch. Extending the unix port to another +architecture requires writing some assembly code for the exception handling +and garbage collection. Alternatively, a fallback implementation based on +setjmp/longjmp can be used. Building -------- @@ -18,7 +19,7 @@ To build the unix port locally then you will need: * git command line executable, unless you downloaded a source .tar.xz file from https://micropython.org/download/ -* gcc (or clang for macOS) toolchain +* an appropriate GCC or Clang toolchain for your target (macOS only supports Clang) * GNU Make * Python 3.x @@ -166,7 +167,7 @@ optimisations, assertions enabled, and debug symbols. ### Sanitizers -Sanitizers are extra runtime checks supported by gcc and clang. The CI process +Sanitizers are extra runtime checks supported by GCC and Clang. The CI process supports building with the "undefined behavior" (UBSan) or "address" (ASan) sanitizers. The script `tools/ci.sh` is the source of truth about how to build and run in these modes. @@ -181,3 +182,125 @@ Several classes of checks are disabled via compiler flags: check is intended to make sure locals in a "returned from" stack frame are not used. However, this mode interferes with various assumptions that MicroPython's stack checking, NLR, and GC rely on. + +### Notes about x86 (i686) support + +It used to be possible to create an x86 (32-bits) build on a x64 (64-bits) host +by passing `MICROPY_FORCE_32BIT=1` to `make`. That option was retired: x86 +usage has declined quite a bit in the past few years, and MicroPython now +supports at least one more mixed 32/64-bits target architecture for which +enabling `MICROPY_FORCE_32BIT` would make builds fail. + +x86 will be treated as a cross-compilation target from now on. This means you +will need to install a suitable compiler (on Ubuntu you can install either the +`gcc-i686-linux-gnu` and `g++-i686-linux-gnu` packages for GCC, or the `clang` +package for Clang, for example) and pass the appropriate command arguments to +`make` depending on which compiler you chose. + +### Building x86 (i686) code with GCC + +For GCC, you will need to pass the toolchain's command prefix to the +`CROSS_COMPILE` command line variable. + +This change is also extended to native modules, for which you may need to pass +the toolchain's command prefix to the `CROSS` command line variable if your +x86 compiler cannot be invoked with `i686-linux-gnu-gcc`. + +Or, as an example: + +```bash +$ printf "%s %s %s %s\n" $(lsb_release -d | cut -f 2) $(uname -m) +Ubuntu 24.04.4 LTS x86_64 + +$ i686-linux-gnu-gcc --version | head -n 1 +i686-linux-gnu-gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 + +$ CROSS_COMPILE=i686-linux-gnu- make -C ports/unix +make: Entering directory '/ports/unix' +Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity. +... +LINK build-standard/micropython + text data bss dec hex filename + 723115 36104 2124 761343 b9dff build-standard/micropython +make: Leaving directory '/ports/unix' + +$ file -b ports/unix/build-standard/micropython | cut -d, -f-2 +ELF 32-bit LSB pie executable, Intel 80386 + +# `i686-linux-gnu-` is the default prefix for x86 native modules now, it has +# been explicitly mentioned here in case you want to see how to set it. + +$ make -C examples/natmod/features0 ARCH=x86 CROSS=i686-linux-gnu- +make: Entering directory '/examples/natmod/features0' +GEN build/features0.config.h +CC features0.c +LINK build/features0.o +arch: EM_386 +text size: 192 +bss size: 0 +GOT entries: 4 +GEN features0.mpy +make: Leaving directory '/examples/natmod/features0' + +$ cd examples/natmod/features0 && ../../../ports/unix/build-standard/micropython +MicroPython v1.29.0-preview.490.gb4c58f7ba5.dirty on 2026-07-04; linux [GCC 13.3.0] version +Type "help()" for more information. +>>> import features0 +>>> features0.factorial(10) +3628800 +``` + +### Building x86 (i686) code with Clang + +For Clang, you will need need to pass both the name of the compiler to invoke +as the `CC` command line variable and the extra command line arguments needed +to let Clang know you are building an i686 binary as the `CFLAGS_EXTRA` and +`LDFLAGS_EXTRA` command line variables (usually +`--target=i686-unknown-linux-gnu`). + +For native modules, since linking is not done by the compiler, only the `CC` +and `CFLAGS_EXTRA` arguments are needed. + +Or, as an example: + +```bash +$ printf "%s %s %s %s\n" $(lsb_release -d | cut -f 2) $(uname -m) +Ubuntu 24.04.4 LTS x86_64 + +$ clang --version | head -1 +Ubuntu clang version 18.1.3 (1ubuntu1) + +$ clang -print-targets | grep -i 32-bit.x86 + x86 - 32-bit X86: Pentium-Pro and above + +$ make -C ports/unix CC=clang CFLAGS_EXTRA="--target=i686-unknown-linux-gnu" LDFLAGS_EXTRA='--target=i686-unknown-linux-gnu' +make: Entering directory '/ports/unix' +Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity. +... +LINK build-standard/micropython + text data bss dec hex filename + 836241 34748 2052 873041 d5251 build-standard/micropython +make: Leaving directory '/ports/unix' + +$ file -b ports/unix/build-standard/micropython | cut -d, -f-2 +ELF 32-bit LSB pie executable, Intel 80386 + +$ make -C examples/natmod/features0 ARCH=x86 CC=clang CFLAGS_EXTRA="--target=i686-unknown-linux-gnu" +make: Entering directory '/examples/natmod/features0' +GEN build-x86/features0.config.h +CC features0.c +LINK build-x86/features0.o +arch: EM_386 +text size: 180 +bss size: 0 +GOT entries: 2 +GEN features0.mpy +make: Leaving directory '/examples/natmod/features0' + +$ cd examples/natmod/features0 && ../../../ports/unix/build-standard/micropython +MicroPython v1.29.0-preview.490.gb4c58f7ba5.dirty on 2026-07-04; linux [Clang 18.1.3] version +Type "help()" for more information. +>>> import features0 +>>> features0.factorial(10) +3628800 +``` diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 49426f0f3e8..8809148652f 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -279,6 +279,28 @@ static mp_obj_t extra_coverage(void) { // calling gc_nbytes with a non-heap pointer mp_printf(&mp_plat_print, "%d\n", (int)gc_nbytes(NULL)); + + // test gc_info_fast + void *p0 = gc_alloc(4, 0); + void *p1 = gc_alloc(4, 0); + void *p2 = gc_alloc(4, 0); + + // Create a hole + gc_free(p1); + + gc_info_t info_slow; + gc_info_t info_fast; + + gc_info(&info_slow); + gc_info_fast(&info_fast); + + // Free allocs + gc_free(p0); + gc_free(p2); + + // Should be equal + mp_printf(&mp_plat_print, "%d\n", info_slow.used == info_fast.used); + mp_printf(&mp_plat_print, "%d\n", info_slow.free == info_fast.free); } // GC initialisation and allocation stress test, to check the logic behind ALLOC_TABLE_GAP_BYTE @@ -346,6 +368,13 @@ static mp_obj_t extra_coverage(void) { gc_collect(); } + // resize one of the blocks + void *before = ptrs[1]; + ptrs[1] = FLIP_POINTER(m_tracked_realloc(FLIP_POINTER(ptrs[1]), 2 * NUM_BYTES)); + void *after = ptrs[1]; + bool location_changed = before != after; + mp_printf(&mp_plat_print, "%d\n", location_changed); + // check the memory blocks have the correct content for (size_t i = 0; i < NUM_PTRS; ++i) { bool correct_contents = true; @@ -364,6 +393,65 @@ static mp_obj_t extra_coverage(void) { } mp_printf(&mp_plat_print, "m_tracked_head = %p\n", MP_STATE_VM(m_tracked_head)); + + // Test realloc with black-box behavioral testing + mp_printf(&mp_plat_print, "# tracked realloc\n"); + + // Test 1: Basic realloc with data preservation + uint8_t *test_ptr = m_tracked_calloc(1, 32); + for (int i = 0; i < 32; i++) { + test_ptr[i] = i; + } + + test_ptr = m_tracked_realloc(test_ptr, 64); // Grow + bool data_preserved = (test_ptr[0] == 0 && test_ptr[31] == 31); + mp_printf(&mp_plat_print, "grow preserves data: %d\n", data_preserved); + + test_ptr = m_tracked_realloc(test_ptr, 16); // Shrink + bool shrink_ok = (test_ptr[0] == 0 && test_ptr[15] == 15); + mp_printf(&mp_plat_print, "shrink preserves data: %d\n", shrink_ok); + + m_tracked_free(test_ptr); + + // Test 2: Multiple allocations + reallocs + GC stability + uint8_t *realloc_ptrs[5]; + for (int i = 0; i < 5; i++) { + realloc_ptrs[i] = m_tracked_calloc(1, 32); + realloc_ptrs[i][0] = 'A' + i; // Mark each + } + + // Realloc some in different positions + realloc_ptrs[0] = m_tracked_realloc(realloc_ptrs[0], 64); // First allocated (tail of list) + realloc_ptrs[2] = m_tracked_realloc(realloc_ptrs[2], 64); // Middle + realloc_ptrs[4] = m_tracked_realloc(realloc_ptrs[4], 64); // Last allocated (head of list) + + // Run GC - if list corrupted, this might crash/fail + gc_collect(); + + // Verify markers intact + bool markers_ok = true; + for (int i = 0; i < 5; i++) { + if (realloc_ptrs[i][0] != 'A' + i) { + markers_ok = false; + break; + } + } + mp_printf(&mp_plat_print, "realloc gc stable: %d\n", markers_ok); + + // Cleanup + for (int i = 0; i < 5; i++) { + m_tracked_free(realloc_ptrs[i]); + } + + // Test 3: Edge cases + uint8_t *null_alloc = m_tracked_realloc(NULL, 32); + null_alloc[0] = 'X'; + mp_printf(&mp_plat_print, "realloc(NULL) ok: %d\n", null_alloc[0] == 'X'); + + void *free_result = m_tracked_realloc(null_alloc, 0); + mp_printf(&mp_plat_print, "realloc(ptr, 0) returns NULL: %d\n", free_result == NULL); + + mp_printf(&mp_plat_print, "m_tracked_head after cleanup: %p\n", MP_STATE_VM(m_tracked_head)); } // vstr @@ -552,6 +640,45 @@ static mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%x%08x\n", (uint32_t)(value_ll >> 32), (uint32_t)value_ll); } + // list argument helpers + { + mp_printf(&mp_plat_print, "# list argument helpers\n"); + + // Create a list to test with + mp_obj_t list_items[] = { mp_const_none, MP_OBJ_NEW_SMALL_INT(77), mp_obj_new_str_from_cstr("hello") }; + size_t list_len = MP_ARRAY_SIZE(list_items); + mp_obj_t list = mp_obj_new_list(list_len, list_items); + + // mp_obj_list_ensure + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_list_ensure(MP_OBJ_NEW_SMALL_INT(-1), 5); // Not a list + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + if (nlr_push(&nlr) == 0) { + mp_obj_list_ensure(list, list_len + 2); // List shorter than minimum length + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + + mp_obj_list_t *as_ptr = mp_obj_list_ensure(list, list_len); // Acceptable! + mp_printf(&mp_plat_print, "mp_obj_list_ensure same list? %d\n", MP_OBJ_TO_PTR(list) == as_ptr); + + // mp_obj_list_optional_arg() + as_ptr = mp_obj_list_optional_arg(list, list_len); + mp_printf(&mp_plat_print, "mp_obj_list_optional_arg same list? %d\n", MP_OBJ_TO_PTR(list) == as_ptr); + + as_ptr = mp_obj_list_optional_arg(mp_const_none, list_len); + mp_printf(&mp_plat_print, "mp_obj_list_optional_arg new list len " SIZE_FMT "\n", as_ptr->len); + + as_ptr = mp_obj_list_optional_arg(MP_OBJ_NULL, list_len); + mp_printf(&mp_plat_print, "mp_obj_list_optional_arg new list from NULL len " SIZE_FMT "\n", as_ptr->len); + } + // runtime utils { mp_printf(&mp_plat_print, "# runtime utils\n"); @@ -617,19 +744,6 @@ static mp_obj_t extra_coverage(void) { mp_emitter_warning(MP_PASS_CODE_SIZE, "test"); } - // binary - { - mp_printf(&mp_plat_print, "# binary\n"); - - // call function with float and double typecodes - float far[1]; - double dar[1]; - mp_binary_set_val_array_from_int('f', far, 0, 123); - mp_printf(&mp_plat_print, "%.0f\n", (double)far[0]); - mp_binary_set_val_array_from_int('d', dar, 0, 456); - mp_printf(&mp_plat_print, "%.0lf\n", dar[0]); - } - // VM { mp_printf(&mp_plat_print, "# VM\n"); diff --git a/ports/unix/main.c b/ports/unix/main.c index 1be6827d626..dc72d79127c 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -147,7 +147,7 @@ static int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu #if defined(MICROPY_UNIX_COVERAGE) // allow to print the parse tree in the coverage build - if (mp_verbose_flag >= 3) { + if (MP_STATE_VM(mp_verbose_flag) >= 3) { printf("----------------\n"); mp_parse_node_print(&mp_plat_print, parse_tree.root, 0); printf("----------------\n"); @@ -674,7 +674,7 @@ MP_NOINLINE int main_(int argc, char **argv) { a += 1; #if MICROPY_DEBUG_PRINTERS } else if (strcmp(argv[a], "-v") == 0) { - mp_verbose_flag++; + MP_STATE_VM(mp_verbose_flag)++; #endif } else if (strncmp(argv[a], "-O", 2) == 0) { if (unichar_isdigit(argv[a][2])) { @@ -733,7 +733,7 @@ MP_NOINLINE int main_(int argc, char **argv) { #endif #if MICROPY_PY_MICROPYTHON_MEM_INFO - if (mp_verbose_flag) { + if (MP_STATE_VM(mp_verbose_flag)) { mp_micropython_mem_info(0, NULL); } #endif diff --git a/ports/unix/modffi.c b/ports/unix/modffi.c index b469e932e0d..c16d40ad3b4 100644 --- a/ports/unix/modffi.c +++ b/ports/unix/modffi.c @@ -446,7 +446,7 @@ static unsigned long long ffi_get_int_value(mp_obj_t o) { return MP_OBJ_SMALL_INT_VALUE(o); } else { unsigned long long res; - mp_obj_int_to_bytes_impl(o, MP_ENDIANNESS_BIG, sizeof(res), (byte *)&res); + mp_obj_int_to_bytes(o, sizeof(res), (byte *)&res, MP_ENDIANNESS_BIG, false, false); return res; } } diff --git a/ports/unix/modjni.c b/ports/unix/modjni.c index dbce61aec11..0a29a94fbd4 100644 --- a/ports/unix/modjni.c +++ b/ports/unix/modjni.c @@ -357,7 +357,7 @@ static void jmethod_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ki (void)kind; mp_obj_jmethod_t *self = MP_OBJ_TO_PTR(self_in); // Variable value printed as cast to int - mp_printf(print, "", qstr_str(self->name)); + mp_printf(print, "", (qstr)self->name); } #define IMATCH(s, static) ((!strncmp(s, static, sizeof(static) - 1)) && (s += sizeof(static) - 1)) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 815be76b4e9..7c2ede3bad3 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -54,13 +54,20 @@ #ifndef MICROPY_PY_SYS_PLATFORM #if defined(__APPLE__) && defined(__MACH__) #define MICROPY_PY_SYS_PLATFORM "darwin" +#elif defined(__FreeBSD__) + #define MICROPY_PY_SYS_PLATFORM "freebsd" #else #define MICROPY_PY_SYS_PLATFORM "linux" #endif #endif #ifndef MICROPY_PY_SYS_PATH_DEFAULT -#define MICROPY_PY_SYS_PATH_DEFAULT ".frozen:~/.micropython/lib:/usr/lib/micropython" +#if defined(__FreeBSD__) +#define SYSTEM_LIB_PATH "/usr/local/lib/micropython" +#else +#define SYSTEM_LIB_PATH "/usr/lib/micropython" +#endif +#define MICROPY_PY_SYS_PATH_DEFAULT ".frozen:~/.micropython/lib:" SYSTEM_LIB_PATH #endif #define MP_STATE_PORT MP_STATE_VM @@ -104,9 +111,12 @@ typedef long mp_off_t; // CIRCUITPY-CHANGE #define MICROPY_ENABLE_SELECTIVE_COLLECT (1) -#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__) || (defined(__riscv) && (__riscv_xlen == 64))) -// Fall back to setjmp() implementation for discovery of GC pointers in registers. -#define MICROPY_GCREGS_SETJMP (1) +// Fall back to setjmp() implementation for discovery of GC pointers in registers +// if running on intel-based macOS, or on architectures for which there is no +// specialised GC pointer discovery mechanism. +#if (defined(__APPLE__) && defined(__MACH__) && (defined(__i386__) || defined(__x86_64__))) || \ + (!(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__) || (defined(__riscv) && __riscv_xlen <= 64) || (defined(__loongarch__) && defined(__loongarch64)))) +#define MICROPY_GCREGS_SETJMP (1) #endif // Enable the VFS, and enable the posix "filesystem". diff --git a/ports/unix/mpconfigport.mk b/ports/unix/mpconfigport.mk index 3c844ccef3b..5260bd46aa1 100644 --- a/ports/unix/mpconfigport.mk +++ b/ports/unix/mpconfigport.mk @@ -1,8 +1,5 @@ # Enable/disable modules and 3rd-party libs to be included in interpreter -# Build 32-bit binaries on a 64-bit host -MICROPY_FORCE_32BIT = 0 - # This variable can take the following values: # 0 - no readline, just simple stdin input # 1 - use MicroPython version of readline diff --git a/ports/unix/mphalport.h b/ports/unix/mphalport.h index d9cd05b3de8..91d44d1688d 100644 --- a/ports/unix/mphalport.h +++ b/ports/unix/mphalport.h @@ -124,5 +124,14 @@ enum { void mp_hal_get_mac(int idx, uint8_t buf[6]); #endif +#if defined(__linux__) && (defined(__ARM_32BIT_STATE) || defined(__riscv)) +#if __has_builtin(__builtin___clear_cache) +#define MP_HAL_CLEAN_DCACHE(fun_data, fun_len) \ + do { \ + __builtin___clear_cache((void *)fun_data, (char *)fun_data + fun_len); \ + } while (0) +#endif +#endif + // Global variable to control compile-only mode. extern bool mp_compile_only; diff --git a/ports/unix/stack_size.h b/ports/unix/stack_size.h index f6159bb69d5..95ec502b0f8 100644 --- a/ports/unix/stack_size.h +++ b/ports/unix/stack_size.h @@ -46,8 +46,15 @@ #define UNIX_STACK_MUL_SANITIZERS 1 #endif -// Double the stack size for 64-bit builds, plus additional scaling -#define UNIX_STACK_MULTIPLIER ((sizeof(void *) / 4) * UNIX_STACK_MUL_ARM * UNIX_STACK_MUL_SANITIZERS) +#if defined(_MSC_VER) +// Similarly Windows seems to require more stack +#define UNIX_STACK_MUL_WINDOWS 2 +#else +#define UNIX_STACK_MUL_WINDOWS 1 +#endif + +// Double the stack size for 64-bit builds, plus additional scalings +#define UNIX_STACK_MULTIPLIER ((sizeof(void *) / 4) * (UNIX_STACK_MUL_ARM)*(UNIX_STACK_MUL_SANITIZERS)*(UNIX_STACK_MUL_WINDOWS)) #endif // UNIX_STACK_MULTIPLIER diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index 54fd4c94ad7..ac75f20eee4 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -117,12 +117,12 @@ void mp_hal_stdio_mode_raw(void) { termios.c_lflag = 0; termios.c_cc[VMIN] = 1; termios.c_cc[VTIME] = 0; - tcsetattr(0, TCSAFLUSH, &termios); + tcsetattr(0, TCSANOW, &termios); } void mp_hal_stdio_mode_orig(void) { // restore terminal settings - tcsetattr(0, TCSAFLUSH, &orig_termios); + tcsetattr(0, TCSANOW, &orig_termios); } #endif diff --git a/ports/unix/variants/coverage/manifest.py b/ports/unix/variants/coverage/manifest.py index 37f2531a842..2d459103ef2 100644 --- a/ports/unix/variants/coverage/manifest.py +++ b/ports/unix/variants/coverage/manifest.py @@ -1,5 +1,5 @@ add_library("unix-ffi", "$(MPY_LIB_DIR)/unix-ffi") freeze_as_str("frzstr") freeze_as_mpy("frzmpy") -freeze_mpy("$(MPY_DIR)/tests/frozen") +freeze_mpy("$(MPY_DIR)/tests/assets") require("ssl") diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 2ef5b0a1bdb..beab74832cb 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -12,8 +12,6 @@ CFLAGS += \ LDFLAGS += -fprofile-arcs -ftest-coverage FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py -# CIRCUITPY-CHANGE: don't include user C modules -# USER_C_MODULES = $(TOP)/examples/usercmodule # CIRCUITPY-CHANGE: use CircuitPython bindings and implementations SRC_QRIO := $(patsubst ../../%,%,$(wildcard ../../shared-bindings/qrio/*.c ../../shared-module/qrio/*.c ../../lib/quirc/lib/*.c)) diff --git a/ports/unix/variants/longlong/mpconfigvariant.h b/ports/unix/variants/longlong/mpconfigvariant.h index d50d360b1fe..1554d537c04 100644 --- a/ports/unix/variants/longlong/mpconfigvariant.h +++ b/ports/unix/variants/longlong/mpconfigvariant.h @@ -32,7 +32,7 @@ // We build it on top of REPR C, which uses memory-efficient floating point // objects encoded directly mp_obj_t (30 bits only). -// Therefore this variant should be built using MICROPY_FORCE_32BIT=1 +// Therefore this variant should be built for a 32-bits target. #define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_C) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) diff --git a/ports/unix/variants/longlong/mpconfigvariant.mk b/ports/unix/variants/longlong/mpconfigvariant.mk index 2d2c3706469..9fe20cd584d 100644 --- a/ports/unix/variants/longlong/mpconfigvariant.mk +++ b/ports/unix/variants/longlong/mpconfigvariant.mk @@ -1,7 +1,7 @@ # build interpreter with "bigints" implemented as "longlong" -# otherwise, small int is essentially 64-bit -MICROPY_FORCE_32BIT := 1 +# This needs to be built for a 32-bits target, otherwise small ints will +# essentially be 64-bit wide. MICROPY_PY_FFI := 0 diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 1ac59c95572..382579ead61 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -83,7 +83,9 @@ #define MICROPY_PY_GC_COLLECT_RETVAL (1) // Enable detailed error messages and warnings. +#ifndef MICROPY_ERROR_REPORTING #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#endif #define MICROPY_WARNINGS (1) #define MICROPY_PY_STR_BYTES_CMP_WARN (1) diff --git a/ports/unix/variants/nanbox/mpconfigvariant.mk b/ports/unix/variants/nanbox/mpconfigvariant.mk index e588e657efc..6d63a86e9b0 100644 --- a/ports/unix/variants/nanbox/mpconfigvariant.mk +++ b/ports/unix/variants/nanbox/mpconfigvariant.mk @@ -1,3 +1,4 @@ # build interpreter with nan-boxing as object model (object repr D) -MICROPY_FORCE_32BIT = 1 +# This needs to be built for a 32-bits target, as object representation D is +# only meant to work on 32-bits machines. diff --git a/ports/zephyr-cp/.gitignore b/ports/zephyr-cp/.gitignore index 65d8deaa722..7cfe421e6b4 100644 --- a/ports/zephyr-cp/.gitignore +++ b/ports/zephyr-cp/.gitignore @@ -1,7 +1,12 @@ -# West manages these folders. +# West manages these folders. modules/* are west clones, except iobroker, +# which is an in-tree Zephyr module (see modules/iobroker/README.md); its +# datasheets/ PDFs are licensed by Nordic and not redistributed here. bootloader build -modules -tools +modules/* +!modules/iobroker +modules/iobroker/datasheets/* +!modules/iobroker/datasheets/README.md +/tools zephyr .west diff --git a/ports/zephyr-cp/CMakeLists.txt b/ports/zephyr-cp/CMakeLists.txt index 9d115f1e176..c276e06178c 100644 --- a/ports/zephyr-cp/CMakeLists.txt +++ b/ports/zephyr-cp/CMakeLists.txt @@ -1,11 +1,23 @@ cmake_minimum_required(VERSION 3.20.0) -find_package(Zephyr REQUIRED HINTS lib/zephyr) +# The dynamic peripheral allocation / runtime pin routing code lives in a +# Zephyr module kept in-tree for now (modules/iobroker, see its README.md). +# It must be set before find_package(Zephyr). Repoint this at the module's new + +# home to consume an externalized copy. +set(ZEPHYR_EXTRA_MODULES + ${CMAKE_CURRENT_SOURCE_DIR}/internal-modules/iobroker + # Kconfig-only module with defaults that must merge before Zephyr's + # subsystem Kconfig files (see internal-modules/circuitpython-kconfig/Kconfig). + ${CMAKE_CURRENT_SOURCE_DIR}/internal-modules/circuitpython-kconfig +) + +find_package(Zephyr REQUIRED HINTS ${ZEPHYR_BASE} $ENV{ZEPHYR_BASE} lib/zephyr) project(circuitpython) target_sources(app PRIVATE zephyr_main.c) -# Add I2C emulator control for native_sim testing +# Add native_sim test support helpers. if(CONFIG_BOARD_NATIVE_SIM) target_sources(app PRIVATE native_sim_i2c_emul_control.c) endif() diff --git a/ports/zephyr-cp/Kconfig b/ports/zephyr-cp/Kconfig index 015864100ec..d407a2797fe 100644 --- a/ports/zephyr-cp/Kconfig +++ b/ports/zephyr-cp/Kconfig @@ -1,104 +1,13 @@ # CircuitPython Zephyr port — application Kconfig # -# Sources Zephyr's main Kconfig, then adds CircuitPython-specific defaults. -# Boards can override any of these in their boards/.conf file. +# Sources Zephyr's main Kconfig. CircuitPython-specific symbol defaults live +# in modules/circuitpython-kconfig/Kconfig, a Kconfig-only Zephyr module: Zephyr +# merges a +# module's Kconfig right after the SoC/board defconfigs and BEFORE its kernel/, +# drivers/, lib/ and subsys/ Kconfigs, so the defaults there take precedence +# over any default Zephyr's subsystem Kconfigs define. This file is merged +# after all of Zephyr, where a Zephyr-side default would silently shadow the +# port's value (first `default` wins). Boards can override any of the defaults +# in their boards///board.conf file. osource "$(ZEPHYR_BASE)/Kconfig.zephyr" - -# ===== Peripheral defaults — enabled by default, boards can disable ===== - -config I2C - default y - -config SPI - default y - -config SPI_ASYNC - default y - -config I2S - default y - -config UART_LINE_CTRL - default y - -config ENTROPY_GENERATOR - default y - -# ===== Bluetooth defaults ===== - -# Use a variable for the chosen name so the comma isn't parsed as an argument separator -DT_BT_HCI_CHOSEN := zephyr,bt-hci - -config BT - default $(dt_chosen_enabled,$(DT_BT_HCI_CHOSEN)) - -config BT_PERIPHERAL - default y - -config BT_CENTRAL - default y - -config BT_BROADCASTER - default y - -config BT_OBSERVER - default y - -config BT_EXT_ADV - default y - -config BT_DEVICE_APPEARANCE_DYNAMIC - default y - -config BT_DEVICE_NAME_DYNAMIC - default y - -config BT_DEVICE_NAME_MAX - default 28 - -config BT_L2CAP_TX_MTU - default 253 - -# BT Buffers -config BT_BUF_CMD_TX_SIZE - default 255 - -config BT_BUF_EVT_RX_COUNT - default 16 - -config BT_BUF_EVT_RX_SIZE - default 255 - -config BT_BUF_ACL_TX_COUNT - default 3 - -config BT_BUF_ACL_TX_SIZE - default 251 - -config BT_BUF_ACL_RX_COUNT_EXTRA - default 1 - -config BT_BUF_ACL_RX_SIZE - default 255 - -config BT_GATT_DYNAMIC_DB - default y - -config BT_GATT_CLIENT - default y - -config BT_SMP - default y - -config BT_BONDABLE - default y - -config SETTINGS - default y - -config BT_SETTINGS - default y - -config BT_KEYS_OVERWRITE_OLDEST - default y diff --git a/ports/zephyr-cp/Kconfig.sysbuild b/ports/zephyr-cp/Kconfig.sysbuild index 11b49446f42..6fbefbe90b9 100644 --- a/ports/zephyr-cp/Kconfig.sysbuild +++ b/ports/zephyr-cp/Kconfig.sysbuild @@ -1,6 +1,9 @@ # Copyright 2023-2024 Nordic Semiconductor ASA # SPDX-License-Identifier: Apache-2.0 +# Whether a board boots via Adaboot is declared by the mcuboot fork itself, in +# bootloader/mcuboot/dts/Kconfig.sysbuild (SB_CONFIG_ADABOOT_BOARD). + source "share/sysbuild/Kconfig" config NET_CORE_BOARD diff --git a/ports/zephyr-cp/Makefile b/ports/zephyr-cp/Makefile index 1fc5040fefa..8e134ca66fa 100644 --- a/ports/zephyr-cp/Makefile +++ b/ports/zephyr-cp/Makefile @@ -1,7 +1,26 @@ -ALL_BOARDS_IN_PORT := $($(wildcard boards/*/*):boards/=:/=) +ALL_BOARDS_IN_PORT := $(patsubst boards/%,%,$(wildcard boards/*/*)) + +# Needed to print the board list one board per line below. +empty := +space := $(empty) $(empty) +define newline + + +endef # An incorrect BOARD might have been specified, so check against the list. -# There is deliberately no space after the := -VALID_BOARD :=$(filter $(BOARD),$(ALL_BOARDS_IN_PORT)) +# Boards are stored as vendor/name but BOARD is the flattened vendor_name, so +# flatten the list the same way (vendors contain no underscores). There is +# deliberately no space after the := +VALID_BOARD :=$(filter $(BOARD),$(subst /,_,$(ALL_BOARDS_IN_PORT))) + +# Fail fast on a mistyped BOARD instead of letting west build a board the port +# isn't configured for (which dies later with confusing Kconfig errors). Targets +# like `clean` don't take a BOARD, so only check when one is given. +ifneq ($(strip $(BOARD)),) +ifeq ($(strip $(VALID_BOARD)),) +$(error Invalid BOARD '$(BOARD)'. Valid boards:$(newline)$(subst $(space),$(newline),$(subst /,_,$(ALL_BOARDS_IN_PORT)))) +endif +endif # If the build directory is not given, make it reflect the board name. BUILD ?= build-$(BOARD) @@ -13,7 +32,7 @@ ifneq ($(strip $(BOARD)),) WEST_SHIELD_ARGS := $(shell SHIELD_ORIGIN="$(origin SHIELD)" SHIELDS_ORIGIN="$(origin SHIELDS)" SHIELD="$(SHIELD)" SHIELDS="$(SHIELDS)" python cptools/get_west_shield_args.py $(BOARD)) endif -WEST_CMAKE_ARGS := -DZEPHYR_BOARD_ALIASES=$(CURDIR)/boards/board_aliases.cmake -Dzephyr-cp_TRANSLATION=$(TRANSLATION) +WEST_CMAKE_ARGS := -DZEPHYR_BOARD_ALIASES=$(CURDIR)/boards/board_aliases.cmake # Board files live in the board's own folder as board.overlay / board.conf. Zephyr only auto-applies # boards/.overlay and boards/.conf named after the RESOLVED Zephyr board, so it finds @@ -31,6 +50,14 @@ WEST_CMAKE_ARGS += -Dzephyr-cp_DTC_OVERLAY_FILE=$(CP_BOARD_OVERLAY) endif CP_BOARD_CONF := $(wildcard $(CP_BOARD_DIR)/board.conf) +# bsim boards link against the BabbleSim simulator checked out under tools/bsim. +# Zephyr's bsim_hw_models module requires these env vars at configure time, so +# export them here instead of relying on the caller's shell having them set. +ifneq ($(findstring bsim,$(BOARD)),) +export BSIM_COMPONENTS_PATH := $(CURDIR)/tools/bsim/components +export BSIM_OUT_PATH := $(CURDIR)/tools/bsim +endif + # When DEBUG=1, apply additional Kconfig fragments for debug-friendly settings. DEBUG_CONF_FILE ?= $(CURDIR)/debug.conf ifeq ($(DEBUG),1) @@ -40,32 +67,111 @@ else CP_BOARD_CONF := $(DEBUG_CONF_FILE) endif endif -ifneq ($(CP_BOARD_CONF),) -WEST_CMAKE_ARGS += -Dzephyr-cp_EXTRA_CONF_FILE=$(CP_BOARD_CONF) +# Always pass the conf file, even if empty. This undoes DEBUG=1 when it's omitted on a later run. +# Quoted because with DEBUG=1 the value is a semicolon-separated list, and an unquoted semicolon +# would end the shell command. +WEST_CMAKE_ARGS += -Dzephyr-cp_EXTRA_CONF_FILE="$(CP_BOARD_CONF)" + +# Boards can opt into building the MCUboot bootloader (and thus BLE OTA +# updates) by shipping a sysbuild.conf fragment. It sets sysbuild-level +# Kconfig (SB_CONFIG_BOOTLOADER_MCUBOOT, bootloader mode, signing keys, ...). +CP_BOARD_SYSBUILD_CONF := $(wildcard $(CP_BOARD_DIR)/sysbuild.conf) +ifneq ($(CP_BOARD_SYSBUILD_CONF),) +WEST_CMAKE_ARGS += -DSB_EXTRA_CONF_FILE=$(CP_BOARD_SYSBUILD_CONF) +endif + +# Boards that enable the bootloader can also tune the MCUboot image itself +# (Kconfig for the bootloader build) via a mcuboot.conf fragment. +CP_BOARD_MCUBOOT_CONF := $(wildcard $(CP_BOARD_DIR)/mcuboot.conf) +ifneq ($(CP_BOARD_MCUBOOT_CONF),) +WEST_CMAKE_ARGS += -Dmcuboot_EXTRA_CONF_FILE=$(CP_BOARD_MCUBOOT_CONF) endif -.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash recover debug debug-jlink debugserver attach run run-sim clean menuconfig all clean-all sim clean-sim test fetch-port-submodules +.PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash recover debug debug-jlink debugserver attach run run-sim clean menuconfig all clean-all sim clean-sim test fetch-port-submodules adaboot -export BSIM_COMPONENTS_PATH := $(CURDIR)/tools/bsim/components -export BSIM_OUT_PATH := $(CURDIR)/tools/bsim +# In the environment, not a cmake argument, so a new language does not reconfigure. +export TRANSLATION + +# west reruns the whole sysbuild configure whenever it gets a cmake argument, so pass +# them only when the build directory is new or they changed since the last configure. +WEST_CONFIGURE_ARGS := -b $(BOARD) $(WEST_SHIELD_ARGS) --sysbuild -- $(WEST_CMAKE_ARGS) +WEST_ARGS_STAMP := $(BUILD)/west_configure_args.txt $(BUILD)/zephyr-cp/zephyr/zephyr.elf: python cptools/pre_zephyr_build_prep.py $(BOARD) - west build -b $(BOARD) -d $(BUILD) $(WEST_SHIELD_ARGS) --sysbuild -- $(WEST_CMAKE_ARGS) + @mkdir -p $(BUILD) + @if [ ! -f $(BUILD)/build.ninja ] || [ "$$(cat $(WEST_ARGS_STAMP) 2>/dev/null)" != '$(WEST_CONFIGURE_ARGS)' ]; then \ + echo west build -d $(BUILD) $(WEST_CONFIGURE_ARGS); \ + echo '$(WEST_CONFIGURE_ARGS)' > $(WEST_ARGS_STAMP); \ + west build -d $(BUILD) $(WEST_CONFIGURE_ARGS) || { rm -f $(WEST_ARGS_STAMP); exit 1; }; \ + else \ + echo west build -d $(BUILD); \ + west build -d $(BUILD); \ + fi python ../../tools/build_memory_info.py $(BUILD)/zephyr-cp/zephyr/zephyr.map $(BUILD) --image $(BUILD)/zephyr-cp/zephyr/zephyr.bin $(BUILD)/firmware.elf: $(BUILD)/zephyr-cp/zephyr/zephyr.elf cp $^ $@ +# Boards the Adaboot fork owns the memory map for (BOARD id minus its vendor +# prefix is the fork's boards.toml key): pull in its generated make fragment, +# which carries MCUBOOT_UF2_APP_BASE -- the app slot's offset within its +# flash device, i.e. the base address .uf2 files must use (Adaboot's UF2 +# writer routes blocks by flash-area offset, see boot/zephyr/uf2_disk.c). +# Like the fork's dts/ tree, the per-board conf/mk fragments live in vendor +# subdirectories; the vendor is the BOARD id's first underscore-separated +# token (the fork's boards.toml keys are board ids minus that prefix). +ADABOOT_VENDOR := $(word 1,$(subst _, ,$(BOARD))) +ADABOOT_KEY := $(subst $(ADABOOT_VENDOR)_,,$(BOARD)) +-include bootloader/mcuboot/conf/$(ADABOOT_VENDOR)/$(ADABOOT_KEY)-autogen.mk + +# MCUBOOT_UF2_APP_BASE is only set for boards that boot via the Adaboot +# MCUboot fork: the app links into the slot0 partition, so the flashable app +# image is the signed one (MCUboot header + hash TLV), and firmware.hex +# carries both the bootloader and signed app images so it can be flashed +# directly. Standalone boards +# (adafruit/raspberrypi UF2-native, native sim, nrf54l15tag) have no +# bootloader domain and ship Zephyr's own unsigned outputs. +ifeq ($(MCUBOOT_UF2_APP_BASE),) + $(BUILD)/firmware.hex: $(BUILD)/zephyr-cp/zephyr/zephyr.elf cp $(BUILD)/zephyr-cp/zephyr/zephyr.hex $@ -$(BUILD)/firmware.exe: $(BUILD)/zephyr-cp/zephyr/zephyr.elf - cp $(BUILD)/zephyr-cp/zephyr/zephyr.exe $@ - $(BUILD)/firmware.uf2: $(BUILD)/zephyr-cp/zephyr/zephyr.elf + # Zephyr generates the UF2 itself, with the board's family ID and + # flash base (CONFIG_BUILD_OUTPUT_UF2*). cp $(BUILD)/zephyr-cp/zephyr/zephyr.uf2 $@ +else + +# west flash programs every sysbuild domain, so ship the same in one hex: +# bootloader, signed app, and the network-core image (if this board builds +# one) so it can be flashed directly with a programmer. The unsigned app +# zephyr.hex also links into slot0 but carries no MCUboot header, so the +# bootloader rejects it. +$(BUILD)/firmware.hex: $(BUILD)/zephyr-cp/zephyr/zephyr.elf $(wildcard $(BUILD)/hci_ipc/zephyr/zephyr.hex) + # hexmerge.py merges the bootloader and app images; it drops the ELF + # start-address records, as a programmer expects. + python zephyr/scripts/build/mergehex.py -o $@ $(BUILD)/mcuboot/zephyr/zephyr.hex $(BUILD)/zephyr-cp/zephyr/zephyr.signed.hex $(wildcard $(BUILD)/hci_ipc/zephyr/zephyr.hex) + +# The signed slot0 image: what an SMP device-manager uploads into slot1 for +# a BLE update (and the payload firmware.uf2 wraps). +$(BUILD)/firmware.signed.bin: $(BUILD)/zephyr-cp/zephyr/zephyr.elf + cp $(BUILD)/zephyr-cp/zephyr/zephyr.signed.bin $@ + +$(BUILD)/firmware.uf2: $(BUILD)/zephyr-cp/zephyr/zephyr.elf + # Base for uf2conv: MCUBOOT_UF2_APP_BASE from the fork's autogen .mk, + # the app slot's offset within its flash device (see the ADABOOT_KEY + # comment above). Family 0xf4b47471 is Adaboot's own; it routes blocks + # by base address, so any consistent non-zero ID is fine but keep it + # stable for bootloaders that check it. + python zephyr/scripts/build/uf2conv.py -c -f 0xf4b47471 -b $(MCUBOOT_UF2_APP_BASE) -o $@ $(BUILD)/zephyr-cp/zephyr/zephyr.signed.bin + +endif + +$(BUILD)/firmware.exe: $(BUILD)/zephyr-cp/zephyr/zephyr.elf + cp $(BUILD)/zephyr-cp/zephyr/zephyr.exe $@ + $(BUILD)/firmware.rps: $(BUILD)/zephyr-cp/zephyr/zephyr.elf cp $(BUILD)/zephyr-cp/zephyr/zephyr.rps $@ @@ -103,6 +209,9 @@ run-sim: menuconfig: west build $(WEST_SHIELD_ARGS) --sysbuild -d $(BUILD) -t menuconfig -- $(WEST_CMAKE_ARGS) +adaboot: + west build -d $(BUILD) --domain mcuboot + clean: rm -rf $(BUILD) @@ -127,7 +236,7 @@ clean-sim: # Every board the test suite uses: native sim (non-asan + asan) and the bsim # boards (tests/bsim/conftest.py parametrizes over both). Zephyr samples for # bsim tests are built on demand by the zephyr_sample fixture. -TEST_BOARDS := native_native_sim native_native_sim_asan native_nrf5340bsim native_nrf54lm20bsim +TEST_BOARDS := native_native_sim native_native_sim_asan native_native_sim_lfs native_nrf5340bsim native_nrf54lm20bsim # Delegate to a sub-make with BOARD set so the per-board build rule (and its # shield args + bsim prep) applies. The targets are phony: the west builds are diff --git a/ports/zephyr-cp/README.md b/ports/zephyr-cp/README.md index 162ed90bcc8..4e895936787 100644 --- a/ports/zephyr-cp/README.md +++ b/ports/zephyr-cp/README.md @@ -99,6 +99,110 @@ Behavior and precedence: - If neither is provided, defaults from `circuitpython.toml` are used. - Use `SHIELD=` (empty) to disable a board default shield for one build. +## Pin names + +Human readable pin names (the `board` module) come from the devicetree by +default: `gpio-leds` and `gpio-keys` labels, node aliases, and connector +`gpio-map`s. Boards can add names without any devicetree involvement by +listing them in `boards///circuitpython.toml` under `[pins]`. +Each entry maps a board module name to the pin number exposed by the board's +hardware: the SoC package pin (or, for ball grid array packages, the +datasheet's ball id, e.g. `"B2"`), or the castellated module pin when the +board uses a module like the Raytac MDBT50Q: + +```toml +[pins] +LED = 17 # QFN package pin number +SDA = "B2" # ball id for BGA/CSP packages +D13 = 8 # MDBT50Q-1MV2 module pin number +``` + +The build resolves each package pin to a SoC pad using the iobroker package +pin map selected by `CONFIG_IOBROKER_PACKAGE_` +(`modules/iobroker/packages/.toml`) and exposes the name on the +matching pad in the `board` module. A name that already maps to the same pin +(from the devicetree or an earlier entry) is deduplicated; a name that maps +to two different pins is a build error. The package map must not be `CUSTOM` +or missing, and the pad must be on an enabled GPIO controller; otherwise the +build fails with an error naming the offending entry. + +## Connector names + +Devicetree connector nodes (`gpio-map`) get their names from a generic +per-compatible list in `cptools/zephyr2cp.py`. A board whose silkscreen +differs can override them per position in `circuitpython.toml` under +`[connectors.]`, keying the gpio-map position (the header pin +number, as a string) to a name: + +```toml +[connectors.nordic_expansion_header] +0 = "EXP_00" # header GPIO 00 +21 = "EXP_21" # header GPIO 21 (QSPI CS) +``` + +Positions left out of the table get no name. + +## OTA firmware updates over BLE + +Boards with Bluetooth and a `slot1_partition` can update CircuitPython +itself over the air. A board opts in by shipping a `sysbuild.conf` in its +board folder: + +``` +boards///sysbuild.conf: + +SB_CONFIG_BOOTLOADER_MCUBOOT=y +``` + +That makes sysbuild build the MCUboot bootloader into the board's +`boot_partition` and link the application into `slot0_partition` (it must +link into slot0 — see `nrf54lm20dk` for an example of a board that does +not). The app image is wrapped with an MCUboot header using imgtool — +signed, or hash-only when the board sets `SB_CONFIG_BOOT_SIGNATURE_TYPE_NONE` +(like `nordic_nrf54l15dk` does). The port Kconfig then enables Zephyr's +MCUmgr/SMP server over the Bluetooth SMP service: + +- `img upload` writes the uploaded image into slot 1 +- `img test`/`img confirm` plus `os reset` reboot into the new image +- the app confirms the running image once it has booted, so an update that + fails to start is automatically reverted (the `TEST_AND_CONFIRM` flow in + the reference clients) + +The service requires an encrypted connection. CircuitPython has no +display or keyboard IO capability, so authenticated (MITM) pairing can +never complete; the OTA boards pair with Just Works instead +(`CONFIG_BT_SMP_ENFORCE_MITM=n` and the SMP service's GATT permissions set +to encryption-only, in the OTA boards' `board.conf`). Once paired and +bonded, the client can upload. + +To update a device, flash it with `make BOARD= flash` (which flashes +both MCUboot and the app) and then use an SMP client such as Nordic's open +source [nRF Connect Device Manager](https://github.com/nordicsemi/Android-nRF-Connect-Device-Manager) +apps (the sample app is what's published on the app stores) or the `mcumgr` +CLI, uploading the signed app image (`zephyr.signed.bin` from the build +directory). + +### Resetting into the bootloader + +Code (and update tools) can enter the bootloader's update mode +programmatically, the way a double-tap of the reset button does: +`supervisor/port.c`'s `reset_to_bootloader()` (invoked by the 1200-bps CDC +touch) and `microcontroller.OnNextReset(RunMode.UF2 / RunMode.BOOTLOADER)` +set a request before resetting, via the adaboot fork's +`adaboot/update_mode.h` helpers. + +The mechanism depends on which bootloader the board boots: fork adaboot +(MCUboot) boards read the boot-mode retention flag, so the app needs +`RETENTION_BOOT_MODE` (defaulted in the port Kconfig for boards whose layout +provides a `zephyr,boot-mode` region) and the board's bootloader conf +(`bootloader/mcuboot/conf//.conf`) needs the matching entrance +option (`MCUBOOT_UF2_ENTRANCE_BOOT_MODE`, or `BOOT_SERIAL_BOOT_MODE` on USB-less +boards like `nrf54l15dk`). Boards that boot the stock Adafruit nRF52 +bootloader (the Adafruit boards) read the request from the raw GPREGRET +register instead and need no bootloader-side changes. + +`nordic_nrf54l15dk` is the first board with this enabled. + ## Testing other boards [Any Zephyr board](https://docs.zephyrproject.org/latest/boards/index.html#) can @@ -116,3 +220,13 @@ west build -b nrf52840dk/nrf52840 ``` This is already supported in `ports/nordic` as `pca10056`. + +The manifest in `zephyr-config/west.yml` skips the HALs of vendors that no board +here uses, so a board from one of them needs its entry removed from the +`name-blocklist` and a `west update` to fetch it. For the +`esp32s3_devkitc` board, that is `hal_espressif`: + +```sh +west update +west build -b esp32s3_devkitc/esp32s3/procpu +``` diff --git a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/autogen_board_info.toml index 334a3e98fb2..bcff337ba37 100644 --- a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/autogen_board_info.toml @@ -13,7 +13,7 @@ adafruit_pixelbuf = false aesio = true alarm = false analogbufio = false -analogio = false +analogio = true # Zephyr board has analogio atexit = false audiobusio = false audiocore = false @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.conf index cfa31e2fdcf..327347edc06 100644 --- a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.conf +++ b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.conf @@ -1,6 +1,18 @@ -CONFIG_USE_DT_CODE_PARTITION=y - CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n +# The board overlay deletes the Zephyr chosen console/shell/mcumgr nodes: +# CircuitPython drives USB CDC ACM through its own usb_cdc bindings. Turn off +# Zephyr's UART console so nothing references the deleted +# DT_CHOSEN(zephyr_console). SERIAL stays on for the dynamically routed UART. +CONFIG_UART_CONSOLE=n +CONFIG_LOG_BACKEND_UART=n + # Enable the ST7789V TFT via the Zephyr display subsystem CONFIG_DISPLAY=y + +# Pin numbers are module pins (MDBT50Q-1MV2 carries the aQFN73 inside). +# The CLUE uses the MDBT50Q-1MV2 module, so pin numbers are module pins. +CONFIG_IOBROKER_PACKAGE_MDBT50Q_1MV2=y + +# SAADC for analogio testing/use (pads resolve to AIN through iobroker) +CONFIG_ADC=y diff --git a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.overlay b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.overlay index 586165dfe06..e0a5aba8f14 100644 --- a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.overlay @@ -1,10 +1,13 @@ +/* Partition layout is owned by the mcuboot fork + * (dts/adafruit/adafruit_clue_nrf52840.dtsi) and applied to this image by + * sysbuild. This overlay only adds board/app config. */ / { chosen { - zephyr,console = &uart0; - zephyr,shell-uart = &uart0; - zephyr,uart-mcumgr = &uart0; - zephyr,bt-mon-uart = &uart0; - zephyr,bt-c2h-uart = &uart0; + /delete-property/ zephyr,console; + /delete-property/ zephyr,shell-uart; + /delete-property/ zephyr,uart-mcumgr; + /delete-property/ zephyr,bt-mon-uart; + /delete-property/ zephyr,bt-c2h-uart; }; }; @@ -12,38 +15,97 @@ /delete-node/ board_cdc_acm_uart; }; +// UART0, I2C1 and SPI3 are enabled with all pins disconnected and marked +// zephyr,deferred-init so nothing is claimed at boot: the iobroker +// initializes and routes a device to arbitrary pins when a busio object is +// constructed, and de-initializes it on release. (I2C0 is the fixed sensor +// bus, SPI2 the fixed display bus, and QSPI the fixed external flash.) +// +// I2C1 and SPI1 are the same peripheral instance (0x40004000), so only one +// mode can be enabled: I2C is the more useful dynamic bus on the CLUE. +// SPI1 is left disabled; UART1 is enabled by neither overlay because UART0 +// already provides a dynamic serial instance. -&gd25q16 { - /delete-node/ partitions; -}; +&pinctrl { + uart0_dyn_default: uart0_dyn_default { + group1 { + psels = , + ; + }; + }; -/delete-node/ &storage_partition; -/delete-node/ &code_partition; + uart0_dyn_sleep: uart0_dyn_sleep { + group1 { + psels = , + ; + }; + }; -&flash0 { - partitions { - code_partition: partition@26000 { - compatible = "zephyr,mapped-partition"; - label = "Application"; - reg = <0x00026000 0x000c4000>; + i2c1_dyn_default: i2c1_dyn_default { + group1 { + psels = , + ; }; + }; + + i2c1_dyn_sleep: i2c1_dyn_sleep { + group1 { + psels = , + ; + }; + }; - storage_partition: partition@ea000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x000ea000 0x00008000>; + spi3_dyn_default: spi3_dyn_default { + group1 { + psels = , + , + ; }; + }; - nvm_partition: partition@f2000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0x000f2000 0x00002000>; + spi3_dyn_sleep: spi3_dyn_sleep { + group1 { + psels = , + , + ; }; }; }; &uart0 { - status = "okay"; + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart0_dyn_default>; + pinctrl-1 = <&uart0_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&i2c1 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c1_dyn_default>; + pinctrl-1 = <&i2c1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +// SPI1 shares the 0x40004000 instance with I2C1, which is enabled above, so +// leave SPI1 disabled. +&spi1 { + status = "disabled"; +}; + +&spi3 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi3_dyn_default>; + pinctrl-1 = <&spi3_dyn_sleep>; + pinctrl-names = "default", "sleep"; }; #include "../../../app.overlay" + +/* Enable the SAADC for analogio (AIN0..AIN7 are on P0.02..P0.05 and P0.28..P0.31). */ +&adc { + status = "okay"; +}; diff --git a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/circuitpython.toml index 4af0c99d878..01eeb66eed0 100644 --- a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/circuitpython.toml @@ -5,3 +5,86 @@ NAME="CLUE nRF52840 Express" # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "nordic/clue_nrf52840_express" + + +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# through the package pin map selected in board.conf. + +[pins] +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# to MDBT50Q-1MV2 module pin numbers through the package pin map selected +# in board.conf. +P0 = 20 +D0 = 20 +A2 = 20 +RX = 20 +P1 = 21 +D1 = 21 +A3 = 21 +TX = 21 +P2 = 9 +D2 = 9 +A4 = 9 +P3 = 13 +D3 = 13 +A5 = 13 +P4 = 11 +D4 = 11 +A6 = 11 +P5 = 50 +D5 = 50 +BUTTON_A = 50 +P6 = 26 +D6 = 26 +P7 = 23 +D7 = 23 +P8 = 58 +D8 = 58 +P9 = 16 +D9 = 16 +P10 = 14 +D10 = 14 +A7 = 14 +P11 = 3 +D11 = 3 +BUTTON_B = 3 +P12 = 12 +D12 = 12 +A0 = 12 +P13 = 24 +D13 = 24 +SCK = 24 +P14 = 22 +D14 = 22 +MISO = 22 +P15 = 19 +D15 = 19 +MOSI = 19 +P16 = 10 +D16 = 10 +A1 = 10 +P17 = 61 +D17 = 61 +L = 61 +LED = 61 +P18 = 38 +D18 = 38 +NEOPIXEL = 38 +P19 = 49 +D19 = 49 +SCL = 49 +P20 = 48 +D20 = 48 +SDA = 48 +MICROPHONE_CLOCK = 18 +MICROPHONE_DATA = 17 +SPEAKER = 47 +PROXIMITY_LIGHT_INTERRUPT = 52 +ACCELEROMETER_GYRO_INTERRUPT = 57 +WHITE_LEDS = 54 +TFT_RESET = 60 +TFT_BACKLIGHT = 59 +TFT_CS = 29 +TFT_DC = 37 +TFT_SCK = 36 +TFT_MOSI = 39 diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml index 90ceb326be4..926fa57252b 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml @@ -13,7 +13,7 @@ adafruit_pixelbuf = false aesio = true alarm = false analogbufio = false -analogio = false +analogio = true # Zephyr board has analogio atexit = false audiobusio = false audiocore = false @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.conf index 6d7299ec6e6..089a4bf94d4 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.conf +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.conf @@ -1,3 +1,8 @@ -CONFIG_USE_DT_CODE_PARTITION=y - CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n + +# Pin numbers are module pins (MDBT50Q-1MV2 carries the aQFN73 inside). +# The feathers use the MDBT50Q-1MV2 module, so pin numbers are module pins. +CONFIG_IOBROKER_PACKAGE_MDBT50Q_1MV2=y + +# SAADC for analogio testing/use (pads resolve to AIN through iobroker) +CONFIG_ADC=y diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.overlay b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.overlay index 586165dfe06..8eb081c2dd4 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.overlay @@ -1,3 +1,6 @@ +/* Partition layout is owned by the mcuboot fork + * (dts/adafruit/adafruit_feather_nrf52840.dtsi) and applied to this image by + * sysbuild. This overlay only adds board/app config. */ / { chosen { zephyr,console = &uart0; @@ -12,38 +15,122 @@ /delete-node/ board_cdc_acm_uart; }; +// Bus instances enabled with all pins disconnected and marked +// zephyr,deferred-init so nothing is claimed at boot: the iobroker +// initializes and routes a device to arbitrary pins when a busio object is +// constructed, and de-initializes it on release: I2C1, SPI2, SPI3 and UART1. +// (The fixed I2C0 is the Stemma QT bus with the SHT31 sensor, and UART0 the +// console. The formerly fixed SPI1 is dropped: it had no device attached and +// its instance is the same peripheral as I2C1, so only one mode can be +// enabled.) -&gd25q16 { - /delete-node/ partitions; -}; +&pinctrl { + i2c1_dyn_default: i2c1_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c1_dyn_sleep: i2c1_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi2_dyn_default: spi2_dyn_default { + group1 { + psels = , + , + ; + }; + }; -/delete-node/ &storage_partition; -/delete-node/ &code_partition; + spi2_dyn_sleep: spi2_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_default: spi3_dyn_default { + group1 { + psels = , + , + ; + }; + }; -&flash0 { - partitions { - code_partition: partition@26000 { - compatible = "zephyr,mapped-partition"; - label = "Application"; - reg = <0x00026000 0x000c4000>; + spi3_dyn_sleep: spi3_dyn_sleep { + group1 { + psels = , + , + ; }; + }; - storage_partition: partition@ea000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x000ea000 0x00008000>; + uart1_dyn_default: uart1_dyn_default { + group1 { + psels = , + ; }; + }; - nvm_partition: partition@f2000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0x000f2000 0x00002000>; + uart1_dyn_sleep: uart1_dyn_sleep { + group1 { + psels = , + ; }; }; }; +&i2c1 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c1_dyn_default>; + pinctrl-1 = <&i2c1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +// SPI1 is the same peripheral instance as I2C1, so leave it disabled. +&spi1 { + status = "disabled"; +}; + +&spi2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi2_dyn_default>; + pinctrl-1 = <&spi2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi3 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi3_dyn_default>; + pinctrl-1 = <&spi3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart1 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart1_dyn_default>; + pinctrl-1 = <&uart1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + &uart0 { status = "okay"; }; #include "../../../app.overlay" + +/* Enable the SAADC for analogio (AIN0..AIN7 are on P0.02..P0.05 and P0.28..P0.31). */ +&adc { + status = "okay"; +}; diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/circuitpython.toml index 5dfa12f79df..0de24263189 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/circuitpython.toml @@ -1,7 +1,53 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf", "uf2"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] USB_VID=0x239A USB_PID=0x8088 NAME="Feather Bluefruit Sense" # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "nordic/feather_bluefruit_sense" + + +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# through the package pin map selected in board.conf. + +[pins] +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# to MDBT50Q-1MV2 module pin numbers through the package pin map selected +# in board.conf. +A0 = 20 +A1 = 21 +A2 = 14 +A3 = 13 +A4 = 11 +A5 = 9 +AREF = 12 +VOLTAGE_MONITOR = 10 +BATTERY = 10 +SWITCH = 50 +NFC1 = 52 +NFC2 = 54 +D2 = 54 +D3 = 4 +D5 = 25 +D6 = 23 +D9 = 19 +D10 = 16 +D11 = 22 +D12 = 24 +D13 = 26 +NEOPIXEL = 38 +SCK = 36 +MOSI = 37 +MISO = 39 +TX = 49 +RX = 48 +SCL = 27 +SDA = 29 +L = 26 +LED = 26 +RED_LED = 26 +BLUE_LED = 3 +MICROPHONE_CLOCK = 18 +MICROPHONE_DATA = 17 +PROXIMITY_LIGHT_INTERRUPT = 47 +ACCELEROMETER_GYRO_INTERRUPT = 4 diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml index 902d9e8a320..860ff6bf6ed 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml @@ -13,7 +13,7 @@ adafruit_pixelbuf = false aesio = true alarm = false analogbufio = false -analogio = false +analogio = true # Zephyr board has analogio atexit = false audiobusio = false audiocore = false @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.conf index 6d7299ec6e6..089a4bf94d4 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.conf +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.conf @@ -1,3 +1,8 @@ -CONFIG_USE_DT_CODE_PARTITION=y - CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n + +# Pin numbers are module pins (MDBT50Q-1MV2 carries the aQFN73 inside). +# The feathers use the MDBT50Q-1MV2 module, so pin numbers are module pins. +CONFIG_IOBROKER_PACKAGE_MDBT50Q_1MV2=y + +# SAADC for analogio testing/use (pads resolve to AIN through iobroker) +CONFIG_ADC=y diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.overlay b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.overlay index 586165dfe06..ee62e624438 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.overlay @@ -1,3 +1,6 @@ +/* Partition layout is owned by the mcuboot fork + * (dts/adafruit/adafruit_feather_nrf52840.dtsi) and applied to this image by + * sysbuild. This overlay only adds board/app config. */ / { chosen { zephyr,console = &uart0; @@ -12,38 +15,121 @@ /delete-node/ board_cdc_acm_uart; }; +// Bus instances enabled with all pins disconnected and marked +// zephyr,deferred-init so nothing is claimed at boot: the iobroker +// initializes and routes a device to arbitrary pins when a busio object is +// constructed, and de-initializes it on release: I2C1, SPI2, SPI3 and UART1. +// (The fixed I2C0 is the Stemma QT bus and UART0 the console. The formerly +// fixed SPI1 is dropped: it had no device attached and its instance is the +// same peripheral as I2C1, so only one mode can be enabled.) -&gd25q16 { - /delete-node/ partitions; -}; +&pinctrl { + i2c1_dyn_default: i2c1_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c1_dyn_sleep: i2c1_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi2_dyn_default: spi2_dyn_default { + group1 { + psels = , + , + ; + }; + }; -/delete-node/ &storage_partition; -/delete-node/ &code_partition; + spi2_dyn_sleep: spi2_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_default: spi3_dyn_default { + group1 { + psels = , + , + ; + }; + }; -&flash0 { - partitions { - code_partition: partition@26000 { - compatible = "zephyr,mapped-partition"; - label = "Application"; - reg = <0x00026000 0x000c4000>; + spi3_dyn_sleep: spi3_dyn_sleep { + group1 { + psels = , + , + ; }; + }; - storage_partition: partition@ea000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x000ea000 0x00008000>; + uart1_dyn_default: uart1_dyn_default { + group1 { + psels = , + ; }; + }; - nvm_partition: partition@f2000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0x000f2000 0x00002000>; + uart1_dyn_sleep: uart1_dyn_sleep { + group1 { + psels = , + ; }; }; }; +&i2c1 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c1_dyn_default>; + pinctrl-1 = <&i2c1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +// SPI1 is the same peripheral instance as I2C1, so leave it disabled. +&spi1 { + status = "disabled"; +}; + +&spi2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi2_dyn_default>; + pinctrl-1 = <&spi2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi3 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi3_dyn_default>; + pinctrl-1 = <&spi3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart1 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart1_dyn_default>; + pinctrl-1 = <&uart1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + &uart0 { status = "okay"; }; #include "../../../app.overlay" + +/* Enable the SAADC for analogio (AIN0..AIN7 are on P0.02..P0.05 and P0.28..P0.31). */ +&adc { + status = "okay"; +}; diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/circuitpython.toml index 8f97aca1d3d..58a36de1eba 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/circuitpython.toml @@ -5,3 +5,46 @@ NAME="Feather nRF52840 Express" # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "nordic/feather_nrf52840_express" + + +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# through the package pin map selected in board.conf. + +[pins] +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# to MDBT50Q-1MV2 module pin numbers through the package pin map selected +# in board.conf. +A0 = 20 +A1 = 21 +A2 = 14 +A3 = 13 +A4 = 11 +A5 = 9 +AREF = 12 +VOLTAGE_MONITOR = 10 +BATTERY = 10 +SWITCH = 50 +NFC1 = 52 +NFC2 = 54 +D2 = 54 +D5 = 25 +D6 = 23 +D9 = 19 +D10 = 16 +D11 = 22 +D12 = 24 +D13 = 26 +NEOPIXEL = 38 +NEOPIXEL_POWER = 7 +SCK = 36 +MOSI = 37 +MISO = 39 +TX = 49 +RX = 48 +SCL = 27 +SDA = 29 +L = 8 +LED = 8 +RED_LED = 8 +D3 = 8 +BLUE_LED = 3 diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml index 05d9ca80a7f..2dcbe9033c3 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.conf index 91c3c15b37d..be49c1e7940 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.conf +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.conf @@ -1 +1,4 @@ CONFIG_GPIO=y + +# Package pin map for the RP2040 QFN-56 used by this board. +CONFIG_IOBROKER_PACKAGE_RP2040_QFN56=y diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.overlay b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.overlay index 71c30f06406..6073f6eda84 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.overlay @@ -1,44 +1,3 @@ -&flash0 { - /delete-node/ partitions; - - partitions { - ranges; - #address-cells = <1>; - #size-cells = <1>; - - /* Reserved memory for the second stage bootloader */ - second_stage_bootloader: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "second_stage_bootloader"; - reg = <0x00000000 0x100>; - read-only; - }; - - code_partition: partition@100 { - compatible = "zephyr,mapped-partition"; - label = "code-partition"; - reg = <0x100 (0xfe000 - 0x100)>; - read-only; - }; - - storage_partition: partition@fe000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0xfe000 0x1000>; - }; - - nvm_partition: partition@ff000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0xff000 0x1000>; - }; - - circuitpy_partition: partition@100000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x100000 (DT_SIZE_M(8) - 0x100000)>; - }; - }; -}; - +/* Partition layout is owned by the mcuboot fork (dts/adafruit/adafruit_feather_rp2040.dtsi) + * and applied to this image by sysbuild. This overlay only adds board/app config. */ #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/circuitpython.toml index 4e992a212f4..d4f273dc636 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/circuitpython.toml @@ -1,4 +1,36 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf", "uf2"] +CIRCUITPY_BUILD_EXTENSIONS = ["uf2"] # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "raspberrypi/adafruit_feather_rp2040" + + +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# through the package pin map selected in board.conf. +[pins] +A0 = 38 +A1 = 39 +A2 = 40 +A3 = 41 +D24 = 36 +D25 = 37 +SCK = 29 +MOSI = 30 +MISO = 31 +D0 = 3 +RX = 3 +D1 = 2 +TX = 2 +D4 = 8 +SDA = 4 +SCL = 5 +D5 = 9 +D6 = 11 +D9 = 12 +D10 = 13 +D11 = 14 +D12 = 15 +LED = 16 +D13 = 16 +BUTTON = 6 +BOOT = 6 +NEOPIXEL = 27 diff --git a/ports/zephyr-cp/boards/board_aliases.cmake b/ports/zephyr-cp/boards/board_aliases.cmake index e1faa7c3888..87362bae0a7 100644 --- a/ports/zephyr-cp/boards/board_aliases.cmake +++ b/ports/zephyr-cp/boards/board_aliases.cmake @@ -36,6 +36,7 @@ cp_board_alias(renesas_ek_ra8d1 ek_ra8d1) cp_board_alias(renesas_da14695_dk_usb da14695_dk_usb) cp_board_alias(native_native_sim native_sim/native) cp_board_alias(native_native_sim_asan native_sim/native) +cp_board_alias(native_native_sim_lfs native_sim/native) cp_board_alias(native_nrf5340bsim nrf5340bsim/nrf5340/cpuapp) cp_board_alias(native_nrf54lm20bsim nrf54lm20bsim/nrf54lm20a/cpuapp) cp_board_alias(nordic_nrf54l15dk nrf54l15dk/nrf54l15/cpuapp) diff --git a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml index e738a839210..53ef46a4b94 100644 --- a/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/native_sim/autogen_board_info.toml @@ -13,7 +13,7 @@ adafruit_pixelbuf = false aesio = true alarm = false analogbufio = false -analogio = false +analogio = true # Zephyr board has analogio atexit = false audiobusio = true # Zephyr board has audiobusio audiocore = true # Zephyr board has audiobusio @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true # Zephyr networking enabled +hmac = false hostnetwork = true # Zephyr board has hostnetwork i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false @@ -98,7 +100,7 @@ sharpdisplay = true # Zephyr board has busio socketpool = true # Zephyr networking enabled spitarget = false ssl = false -storage = true +storage = true # Zephyr board has flash struct = true supervisor = true synthio = true # Zephyr board has audiobusio diff --git a/ports/zephyr-cp/boards/native/native_sim/board.conf b/ports/zephyr-cp/boards/native/native_sim/board.conf index a550d70dacc..0937f18c87e 100644 --- a/ports/zephyr-cp/boards/native/native_sim/board.conf +++ b/ports/zephyr-cp/boards/native/native_sim/board.conf @@ -1,6 +1,10 @@ # No Bluetooth hardware on native_sim CONFIG_BT=n +# native_sim has no physical package: use the identity package pin map so +# pin objects (LED, P_00..P_31) resolve to their GPIO controller. +CONFIG_IOBROKER_PACKAGE_ONE_TO_ONE=y + CONFIG_EMUL=y CONFIG_GPIO=y CONFIG_NATIVE_SIM_SLOWDOWN_TO_REAL_TIME=n @@ -28,6 +32,14 @@ CONFIG_EEPROM=y CONFIG_EEPROM_AT24=y CONFIG_EEPROM_AT2X_EMUL=y +# ADC emulation for analogio testing +CONFIG_ADC=y +CONFIG_ADC_EMUL=y + +# DAC emulation for analogio testing (the vnd,dac node in the board overlay +# turns on CONFIG_DAC_TEST) +CONFIG_DAC=y + # I2S SDL emulation for audio testing CONFIG_I2S_SDL=y diff --git a/ports/zephyr-cp/boards/native/native_sim/board.overlay b/ports/zephyr-cp/boards/native/native_sim/board.overlay index d59a13db80d..23c0c88cb67 100644 --- a/ports/zephyr-cp/boards/native/native_sim/board.overlay +++ b/ports/zephyr-cp/boards/native/native_sim/board.overlay @@ -7,6 +7,9 @@ #include +/* Partition layout is owned by the mcuboot fork (dts/native/native_sim.dtsi) + * and applied to this image by sysbuild. This overlay only adds board/app config. */ + / { sram0: memory@20000000 { device_type = "memory"; @@ -18,33 +21,6 @@ chosen { zephyr,sram = &sram0; /delete-property/ zephyr,flash; - /delete-property/ zephyr,code-partition; - }; -}; - -&flash0 { - /delete-node/ partitions; - partitions { - #address-cells = <1>; - #size-cells = <1>; - - circuitpy_partition: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x00000000 DT_SIZE_K(2032)>; - }; - - storage_partition: partition@1fc000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x001fc000 DT_SIZE_K(8)>; - }; - - nvm_partition: partition@1fe000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0x001fe000 0x00002000>; - }; }; }; @@ -60,4 +36,21 @@ }; }; +/* Expand ADC channels for analogio tests. */ +&adc0 { + nchannels = <4>; + ref-internal-mv = <3300>; + ref-vdd-mv = <3300>; +}; + +/ { + dac0: dac { + compatible = "vnd,dac"; + status = "okay"; + #io-channel-cells = <1>; + vref-mv = <3300>; + }; +}; + + #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/native/native_sim_asan/autogen_board_info.toml b/ports/zephyr-cp/boards/native/native_sim_asan/autogen_board_info.toml index e738a839210..53ef46a4b94 100644 --- a/ports/zephyr-cp/boards/native/native_sim_asan/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/native_sim_asan/autogen_board_info.toml @@ -13,7 +13,7 @@ adafruit_pixelbuf = false aesio = true alarm = false analogbufio = false -analogio = false +analogio = true # Zephyr board has analogio atexit = false audiobusio = true # Zephyr board has audiobusio audiocore = true # Zephyr board has audiobusio @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true # Zephyr networking enabled +hmac = false hostnetwork = true # Zephyr board has hostnetwork i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false @@ -98,7 +100,7 @@ sharpdisplay = true # Zephyr board has busio socketpool = true # Zephyr networking enabled spitarget = false ssl = false -storage = true +storage = true # Zephyr board has flash struct = true supervisor = true synthio = true # Zephyr board has audiobusio diff --git a/ports/zephyr-cp/boards/native/native_sim_asan/board.conf b/ports/zephyr-cp/boards/native/native_sim_asan/board.conf index 62ebc476a8e..72f02a30094 100644 --- a/ports/zephyr-cp/boards/native/native_sim_asan/board.conf +++ b/ports/zephyr-cp/boards/native/native_sim_asan/board.conf @@ -4,6 +4,10 @@ CONFIG_ASAN=y # No Bluetooth hardware on native_sim CONFIG_BT=n +# native_sim has no physical package: use the identity package pin map so +# pin objects (LED, P_00..P_31) resolve to their GPIO controller. +CONFIG_IOBROKER_PACKAGE_ONE_TO_ONE=y + CONFIG_EMUL=y CONFIG_GPIO=y CONFIG_NATIVE_SIM_SLOWDOWN_TO_REAL_TIME=n @@ -31,6 +35,14 @@ CONFIG_EEPROM=y CONFIG_EEPROM_AT24=y CONFIG_EEPROM_AT2X_EMUL=y +# ADC emulation for analogio testing +CONFIG_ADC=y +CONFIG_ADC_EMUL=y + +# DAC emulation for analogio testing (the vnd,dac node in the board overlay +# turns on CONFIG_DAC_TEST) +CONFIG_DAC=y + # I2S SDL emulation for audio testing CONFIG_I2S_SDL=y diff --git a/ports/zephyr-cp/boards/native/native_sim_lfs/autogen_board_info.toml b/ports/zephyr-cp/boards/native/native_sim_lfs/autogen_board_info.toml new file mode 100644 index 00000000000..46c81b6d5eb --- /dev/null +++ b/ports/zephyr-cp/boards/native/native_sim_lfs/autogen_board_info.toml @@ -0,0 +1,133 @@ +# This file is autogenerated when a board is built. Do not edit. Do commit it to git. Other scripts use its info. +name = "zephyr Native simulator - littlefs filesystem" + +[modules] +__future__ = true +_bleio = false +_eve = false +_pew = false +_pixelmap = false +_stage = false +adafruit_bus_device = true +adafruit_pixelbuf = false +aesio = true +alarm = false +analogbufio = false +analogio = true # Zephyr board has analogio +atexit = false +audiobusio = true # Zephyr board has audiobusio +audiocore = true # Zephyr board has audiobusio +audiodelays = true # Zephyr board has audiobusio +audiofilewriter = false +audiofilters = true # Zephyr board has audiobusio +audiofreeverb = true # Zephyr board has audiobusio +audioi2sin = false +audioio = false +audiomixer = true # Zephyr board has audiobusio +audiomp3 = true # Zephyr board has audiobusio +audiopwmio = false +audiospeed = false +aurora_epaper = false +bitbangio = false +bitmapfilter = true # Zephyr board has busio +bitmaptools = true # Zephyr board has busio +bitops = false +board = true # Always enabled +busdisplay = true # Zephyr board has busio +busio = true # Zephyr board has busio +camera = false +canio = false +codeop = false +countio = false +digitalio = true +displayio = true # Zephyr board has displayio +dotclockframebuffer = false +dualbank = false +emmcio = false +epaperdisplay = true # Zephyr board has busio +floppyio = false +fontio = true # Zephyr board has busio +fourwire = true # Zephyr board has busio +framebufferio = true # Zephyr board has busio +frequencyio = false +getpass = true +gifio = true # Zephyr board has busio +gnss = false +hardwarekey = false +hashlib = true # Zephyr networking enabled +hmac = false +hostnetwork = true # Zephyr board has hostnetwork +i2cdisplaybus = true # Zephyr board has busio +i2cioexpander = false +i2ctarget = false +imagecapture = false +ipaddress = true # Zephyr networking enabled +is31fl3741 = false +jpegio = true # Zephyr board has busio +keypad = false +keypad_demux = false +locale = false +lvfontio = true # Zephyr board has busio +math = true +max3421e = false +mcp4822 = false +mdns = false +memorymap = false +memorymonitor = false +microcontroller = true +mipidsi = false +msgpack = true +neopixel_write = false +nvm = true # Board has NVM partition +onewireio = false +os = true +paralleldisplaybus = false +picogame = false +ps2io = false +pulseio = false +pwmio = false +qrio = false +qspibus = false +rainbowio = true +random = true +rclcpy = false +rgbmatrix = false +rotaryio = true # Zephyr board has rotaryio +rtc = false +sdcardio = true # Zephyr board has busio +sdioio = false +sharpdisplay = true # Zephyr board has busio +socketpool = true # Zephyr networking enabled +spitarget = false +ssl = false +storage = true # Zephyr board has flash +struct = true +supervisor = true +synthio = true # Zephyr board has audiobusio +terminalio = true # Zephyr board has busio +tilepalettemapper = true # Zephyr board has busio +time = true +touchio = false +traceback = true +uheap = false +usb = false +usb_audio = false +usb_cdc = false +usb_hid = false +usb_host = false +usb_midi = false +usb_video = false +ustack = false +vectorio = true # Zephyr board has busio +warnings = true +watchdog = false +wifi = false +zephyr_display = true # Zephyr board has zephyr_display +zephyr_kernel = false +zlib = true +# extmod modules shared with MicroPython +asyncio = true +binascii = true +json = true +re = true +select = true diff --git a/ports/zephyr-cp/boards/native/native_sim_lfs/board.conf b/ports/zephyr-cp/boards/native/native_sim_lfs/board.conf new file mode 100644 index 00000000000..c80b9981ff1 --- /dev/null +++ b/ports/zephyr-cp/boards/native/native_sim_lfs/board.conf @@ -0,0 +1,56 @@ +# No Bluetooth hardware on native_sim +CONFIG_BT=n + +CONFIG_EMUL=y +CONFIG_GPIO=y +CONFIG_NATIVE_SIM_SLOWDOWN_TO_REAL_TIME=n + +# So we can test safe mode +CONFIG_NATIVE_SIM_REBOOT=y + +CONFIG_TRACING=y +CONFIG_TRACING_PERFETTO=y +CONFIG_TRACING_SYNC=y +CONFIG_TRACING_BACKEND_POSIX=y +CONFIG_TRACING_GPIO=y + +# I2C emulation for testing +CONFIG_I2C_EMUL=y + +# Display emulation for display/terminal golden tests. +CONFIG_DISPLAY=y +CONFIG_SDL_DISPLAY=y +# Don't require hardware acceleration so the software renderer can be used. +CONFIG_SDL_DISPLAY_USE_HARDWARE_ACCELERATOR=n + +# EEPROM emulation for testing +CONFIG_EEPROM=y +CONFIG_EEPROM_AT24=y +CONFIG_EEPROM_AT2X_EMUL=y + +# ADC emulation for analogio testing +CONFIG_ADC=y +CONFIG_ADC_EMUL=y + +# DAC emulation for analogio testing (the vnd,dac node in the board overlay +# turns on CONFIG_DAC_TEST) +CONFIG_DAC=y + +# I2S SDL emulation for audio testing +CONFIG_I2S_SDL=y + +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_ETH_NATIVE_TAP=n +CONFIG_NET_DRIVERS=y +CONFIG_NET_SOCKETS_OFFLOAD=y +CONFIG_NET_NATIVE_OFFLOADED_SOCKETS=y +CONFIG_HEAP_MEM_POOL_SIZE=1024 + +CONFIG_NET_LOG=y + +CONFIG_MBEDTLS=y +CONFIG_PSA_WANT_ALG_SHA_1=y +CONFIG_PSA_WANT_ALG_SHA_256=y diff --git a/ports/zephyr-cp/boards/native/native_sim_lfs/board.overlay b/ports/zephyr-cp/boards/native/native_sim_lfs/board.overlay new file mode 100644 index 00000000000..495a16de5b7 --- /dev/null +++ b/ports/zephyr-cp/boards/native/native_sim_lfs/board.overlay @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Device tree overlay for CircuitPython on native_sim (littlefs filesystem). + * Same as the native_sim overlay, but the user filesystem partition node is + * named littlefs_partition to match the supervisor's littlefs build. + */ + +#include + +/* The mcuboot fork's layout (dts/native/native_sim.dtsi) is applied to this + * image first and names the filesystem node fatfs_partition. Replace the whole + * partition table so the filesystem node is littlefs_partition; the supervisor + * opens the filesystem by that nodelabel. The geometry must match the fork's + * layout: filesystem, then 8 KB storage, then 8 KB nvm. */ + +&flash0 { + /delete-node/ partitions; + + partitions { + #address-cells = <1>; + #size-cells = <1>; + + littlefs_partition: partition@0 { + compatible = "zephyr,mapped-partition"; + label = "filesystem"; + reg = <0x00000000 DT_SIZE_K(2032)>; + }; + + storage_partition: partition@1fc000 { + compatible = "zephyr,mapped-partition"; + label = "storage"; + reg = <0x001fc000 DT_SIZE_K(8)>; + }; + + nvm_partition: partition@1fe000 { + compatible = "zephyr,mapped-partition"; + label = "nvm"; + reg = <0x001fe000 0x00002000>; + }; + }; +}; + +/ { + sram0: memory@20000000 { + device_type = "memory"; + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x20000000 DT_SIZE_M(1)>; + zephyr,memory-region = "SRAM"; + }; + + chosen { + zephyr,sram = &sram0; + /delete-property/ zephyr,flash; + }; +}; + +/* Add emulated I2C devices for testing */ +&i2c0 { + at24_eeprom: eeprom@50 { + compatible = "atmel,at24"; + reg = <0x50>; + size = <256>; + pagesize = <8>; + address-width = <8>; + timeout = <5>; + }; +}; + +/* Expand ADC channels for analogio tests. */ +&adc0 { + nchannels = <4>; + ref-internal-mv = <3300>; + ref-vdd-mv = <3300>; +}; + +/ { + dac0: dac { + compatible = "vnd,dac"; + status = "okay"; + #io-channel-cells = <1>; + vref-mv = <3300>; + }; +}; + + +#include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/native/native_sim_lfs/circuitpython.toml b/ports/zephyr-cp/boards/native/native_sim_lfs/circuitpython.toml new file mode 100644 index 00000000000..f98422e04c7 --- /dev/null +++ b/ports/zephyr-cp/boards/native/native_sim_lfs/circuitpython.toml @@ -0,0 +1,2 @@ +NAME = "Native simulator - littlefs filesystem" +CIRCUITPY_BUILD_EXTENSIONS = ["exe"] diff --git a/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml index 737f642dea3..add02435bf1 100644 --- a/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/nrf5340bsim/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false diff --git a/ports/zephyr-cp/boards/native/nrf5340bsim/board.overlay b/ports/zephyr-cp/boards/native/nrf5340bsim/board.overlay index 5f0c9fd7af8..4a1693ce39f 100644 --- a/ports/zephyr-cp/boards/native/nrf5340bsim/board.overlay +++ b/ports/zephyr-cp/boards/native/nrf5340bsim/board.overlay @@ -1,5 +1,8 @@ /* SPDX-License-Identifier: Apache-2.0 */ +/* Partition layout is owned by the mcuboot fork (dts/native/nrf5340bsim.dtsi) + * and applied to this image by sysbuild. This overlay only adds board/app config. */ + / { chosen { zephyr,sram = &sram0; @@ -11,24 +14,4 @@ zephyr,memory-region = "SRAM"; }; -&flash0 { - /delete-node/ partitions; - partitions { - #address-cells = <1>; - #size-cells = <1>; - - circuitpy_partition: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x00000000 DT_SIZE_K(1008)>; - }; - - storage_partition: partition@fc000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x000fc000 DT_SIZE_K(16)>; - }; - }; -}; - /* Note: bsim doesn't have USB, so we don't include app.overlay */ diff --git a/ports/zephyr-cp/boards/native/nrf54lm20bsim/autogen_board_info.toml b/ports/zephyr-cp/boards/native/nrf54lm20bsim/autogen_board_info.toml index fba8b5ebd52..ddef2c91f37 100644 --- a/ports/zephyr-cp/boards/native/nrf54lm20bsim/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/native/nrf54lm20bsim/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false diff --git a/ports/zephyr-cp/boards/native/nrf54lm20bsim/board.overlay b/ports/zephyr-cp/boards/native/nrf54lm20bsim/board.overlay index ed5831ec5a3..c627a95c76e 100644 --- a/ports/zephyr-cp/boards/native/nrf54lm20bsim/board.overlay +++ b/ports/zephyr-cp/boards/native/nrf54lm20bsim/board.overlay @@ -1,5 +1,8 @@ /* SPDX-License-Identifier: Apache-2.0 */ +/* Partition layout is owned by the mcuboot fork (dts/native/nrf54lm20bsim.dtsi) + * and applied to this image by sysbuild. This overlay only adds board/app config. */ + / { sram0: memory@20000000 { device_type = "memory"; @@ -13,25 +16,4 @@ }; }; -&cpuapp_rram { - /delete-node/ partitions; - partitions { - compatible = "fixed-partitions"; - #address-cells = <1>; - #size-cells = <1>; - - circuitpy_partition: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x00000000 (DT_SIZE_K(2036) - DT_SIZE_K(32))>; - }; - - storage_partition: partition@1f5000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x001f5000 DT_SIZE_K(32)>; - }; - }; -}; - /* Note: bsim doesn't have USB, so we don't include app.overlay */ diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index 2c2194316a1..5467945ca46 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf5340dk/board.overlay index 6b7ee85de7c..9bfeb75d44b 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/board.overlay @@ -21,12 +21,63 @@ i2s_rxtx: &i2s0 { clock-source = "ACLK"; }; -/* Remove slot1 (OTA), expand slot0 to use the space. - * CircuitPython doesn't use OTA updates. */ -&slot0_partition { - reg = <0x00010000 0x000E0000>; +// Enable free serial peripheral instances so CircuitPython can mux them to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 0 is left alone: uart0 is the console and i2c0/spi0 share that +// same peripheral, so they must not be enabled. Instance 1 stays with the +// fixed arduino header i2c1, and spi4 stays the fixed arduino header SPI. + +&pinctrl { + i2c2_dyn_default: i2c2_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c2_dyn_sleep: i2c2_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + uart3_dyn_default: uart3_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart3_dyn_sleep: uart3_dyn_sleep { + group1 { + psels = , + ; + }; + }; }; -/delete-node/ &slot1_partition; +&i2c2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c2_dyn_default>; + pinctrl-1 = <&i2c2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart3 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart3_dyn_default>; + pinctrl-1 = <&uart3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/circuitpython.toml index cc7e3ae8c4a..f0a4cd45cfd 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/circuitpython.toml @@ -1,3 +1,3 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2", "signed.bin"] USB_VID=0x239A USB_PID=0x8166 diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml index 69b7eab3840..4c7534286de 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = false # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf54h20dk/board.overlay index e557179992b..3654794d047 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54h20dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/board.overlay @@ -1,17 +1,3 @@ -&gpio6 { - status = "okay"; - zephyr,pm-device-runtime-auto; -}; - -&exmif { - status = "okay"; - zephyr,pm-device-runtime-auto; -}; - -&mx25uw63 { - status = "okay"; -}; - /* * SDA = P2.11 * SCL = P2.10 @@ -44,12 +30,99 @@ status = "okay"; }; -/* Remove slot1 (OTA), expand slot0 to use the space. - * CircuitPython doesn't use OTA updates. */ -&slot0_partition { - reg = <0x40000 DT_SIZE_K(656)>; +/* The flash layout comes from the mcuboot fork's generated + * nrf54h20dk-partitions.dtsi: slot0 is expanded to 1.3 MB, the OTA slot1 + * lives on external flash and the cpurad slots are absorbed, so no + * partition fix-ups are needed here. + */ + +// Enable free serial peripheral instances so CircuitPython can mux them to +// arbitrary pins at runtime. On this SoC the i2c/spi/uart nodes with the same +// instance number are the same peripheral block (e.g. i2c130/spi130/uart130 +// all sit at 0x9a5000), so each instance is enabled in exactly one mode. All +// signals are disconnected and the devices are marked zephyr,deferred-init so +// nothing is claimed at boot; iobroker initializes and routes a device when a +// busio object is constructed and de-initializes it on release. +// +// Instance 130 stays with the fixed arduino header i2c130. Instances 120, +// 135 and 136 are left alone: uart120 and uart135 are the console UARTs of +// the (not built here) FLPR and PPR cores, and uart136 is the app console. +// The DMA region is required on this SoC: the serial drivers only access +// memory the peripheral's EasyDMA is allowed to reach. + +&pinctrl { + i2c131_dyn_default: i2c131_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c131_dyn_sleep: i2c131_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi132_dyn_default: spi132_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi132_dyn_sleep: spi132_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + uart137_dyn_default: uart137_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart137_dyn_sleep: uart137_dyn_sleep { + group1 { + psels = , + ; + }; + }; }; -/delete-node/ &slot1_partition; +&i2c131 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c131_dyn_default>; + pinctrl-1 = <&i2c131_dyn_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,concat-buf-size = <256>; + memory-regions = <&cpuapp_dma_region>; +}; + +&spi132 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi132_dyn_default>; + pinctrl-1 = <&spi132_dyn_sleep>; + pinctrl-names = "default", "sleep"; + memory-regions = <&cpuapp_dma_region>; +}; + +&uart137 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart137_dyn_default>; + pinctrl-1 = <&uart137_dyn_sleep>; + pinctrl-names = "default", "sleep"; + memory-regions = <&cpuapp_dma_region>; +}; #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml index 7cf2501b2b1..15d36f2576f 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml @@ -1,2 +1,2 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf", "hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] DISABLED_MODULES=["jpegio", "gifio", "tilepalettemapper"] diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml index a90aa98af34..2e642d21f3e 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.conf b/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.conf new file mode 100644 index 00000000000..184a388a993 --- /dev/null +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.conf @@ -0,0 +1,14 @@ +# Enable the controller's Zephyr VS HCI set/get TX power commands, which +# _bleio's start_advertising(tx_power=...) uses. Without this the controller +# rejects the commands and tx power is fixed at the BT_CTLR_TX_PWR default (0 dBm). +# The nRF54L15 radio supports -46 to +8 dBm. +CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y + +# Work around a latent bug in soc_flash_nrf_ticker.c: with the default +# NRF_RRAM_WRITE_BUFFER_SIZE=1, FLASH_SLOT_WRITE (500 us) < +# FLASH_SYNC_SWITCHING_TIME (1700 us), so `interval = duration - +# FLASH_SYNC_SWITCHING_TIME` underflows and the next flash slot is scheduled +# ~71 minutes out. Any write larger than a single 16-byte slot then times out +# (-ETIMEDOUT / -116), e.g. persisting bond keys right after pairing. +# See zephyr/tests/boards/nrf/rram/overlay-radio_sync.conf. +CONFIG_NRF_RRAM_WRITE_BUFFER_SIZE=32 diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.overlay index 7ffd2dec4d6..e4dc1bc7802 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.overlay @@ -1,28 +1,90 @@ // nRF54L15 DK doesn't have USB, so no app.overlay for CDC ACM. -// I2C bus on P1.8 (SDA) and P1.10 (SCL). +// Enable every free serial peripheral instance so CircuitPython can mux it to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 20 is left alone: uart20 is the console (main UART) and i2c20/spi20 +// share that same peripheral, so they must not be enabled. Instance 00 is +// owned by spi00 and the MX25R64 SPI flash (no TWI00 on this SoC). + &pinctrl { - i2c21_default: i2c21_default { + i2c21_dyn_default: i2c21_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c21_dyn_sleep: i2c21_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi22_dyn_default: spi22_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi22_dyn_sleep: spi22_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + uart30_dyn_default: uart30_dyn_default { group1 { - psels = , - ; - bias-pull-up; + psels = , + ; }; }; - i2c21_sleep: i2c21_sleep { + uart30_dyn_sleep: uart30_dyn_sleep { group1 { - psels = , - ; - low-power-enable; + psels = , + ; }; }; }; &i2c21 { - clock-frequency = ; - pinctrl-0 = <&i2c21_default>; - pinctrl-1 = <&i2c21_sleep>; + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c21_dyn_default>; + pinctrl-1 = <&i2c21_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi22 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi22_dyn_default>; + pinctrl-1 = <&spi22_dyn_sleep>; pinctrl-names = "default", "sleep"; +}; + +&uart30 { status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart30_dyn_default>; + pinctrl-1 = <&uart30_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +/ { + chosen { + zephyr,code-partition = &slot0_partition; + }; }; diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/circuitpython.toml index 00c797b1dad..a0c0d5129bc 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/circuitpython.toml @@ -1 +1 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf", "hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "signed.bin"] diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15tag/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15tag/autogen_board_info.toml index b5bc0dee33b..221e93ac4b8 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15tag/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15tag/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.conf b/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.conf index 2627ebe2e5f..dc4084719cc 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.conf +++ b/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.conf @@ -2,3 +2,18 @@ CONFIG_SERIAL=y CONFIG_CONSOLE=y CONFIG_UART_CONSOLE=y + +# Enable the controller's Zephyr VS HCI set/get TX power commands, which +# _bleio's start_advertising(tx_power=...) uses. Without this the controller +# rejects the commands and tx power is fixed at the BT_CTLR_TX_PWR default (0 dBm). +# The nRF54L15 radio supports -46 to +8 dBm. +CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y + +# Work around a latent bug in soc_flash_nrf_ticker.c: with the default +# NRF_RRAM_WRITE_BUFFER_SIZE=1, FLASH_SLOT_WRITE (500 us) < +# FLASH_SYNC_SWITCHING_TIME (1700 us), so `interval = duration - +# FLASH_SYNC_SWITCHING_TIME` underflows and the next flash slot is scheduled +# ~71 minutes out. Any write larger than a single 16-byte slot then times out +# (-ETIMEDOUT / -116), e.g. persisting bond keys right after pairing. +# See zephyr/tests/boards/nrf/rram/overlay-radio_sync.conf. +CONFIG_NRF_RRAM_WRITE_BUFFER_SIZE=32 diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.overlay b/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.overlay index ddf5c8f7e8f..6dd08bfce59 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.overlay @@ -37,3 +37,65 @@ zephyr,uart-mcumgr = &uart30; }; }; + +// Enable free serial peripheral instances so CircuitPython can mux them to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 21 stays with the fixed i2c21 (bme688 and adxl367 hang off it) and +// instance 22 with the fixed spi22 (bmi270). Instance 30 is left alone: +// uart30 is the console above and i2c30/spi30 share that same peripheral, so +// they must not be enabled. There is no i2c00 on this SoC, so instance 00 can +// only carry spi00 (or uart00). + +&pinctrl { + i2c20_dyn_default: i2c20_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c20_dyn_sleep: i2c20_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi00_dyn_default: spi00_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi00_dyn_sleep: spi00_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; +}; + +&i2c20 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c20_dyn_default>; + pinctrl-1 = <&i2c20_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi00 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi00_dyn_default>; + pinctrl-1 = <&spi00_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; diff --git a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/autogen_board_info.toml index 828e2eaafbf..b7fc23a8413 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/board.overlay index 0f48a5a5d46..1f8639f82a1 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/board.overlay @@ -1,52 +1,131 @@ // nRF54LM20 DK has USB. Include the main app overlay for USB CDC ACM console and data. #include "../../../app.overlay" -// Link application starting at 0x0 instead of slot0_partition (no mcuboot yet). -/ { - chosen { - zephyr,code-partition = &cpuapp_rram; +// Enable every free serial peripheral instance so CircuitPython can mux it to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 20 is left alone: uart20 is the console (main UART) and i2c20/spi20 +// share that same peripheral, so they must not be enabled. Instance 00 is +// owned by spi00 and the MX25R64 SPI flash, so uart00 must not be enabled. + +&pinctrl { + i2c21_dyn_default: i2c21_dyn_default { + group1 { + psels = , + ; + }; }; -}; -// Enable the external MX25R6435F 8MB QSPI flash (connected via SPIM00). -&mx25r64 { - status = "okay"; + i2c21_dyn_sleep: i2c21_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + i2c23_dyn_default: i2c23_dyn_default { + group1 { + psels = , + ; + }; + }; - partitions { - compatible = "fixed-partitions"; - #address-cells = <1>; - #size-cells = <1>; + i2c23_dyn_sleep: i2c23_dyn_sleep { + group1 { + psels = , + ; + }; + }; - circuitpy_partition: partition@0 { - label = "circuitpy"; - reg = <0x00000000 DT_SIZE_M(8)>; + spi22_dyn_default: spi22_dyn_default { + group1 { + psels = , + , + ; }; }; -}; -// I2C bus on P1.13 (SDA) and P1.23 (SCL). -&pinctrl { - i2c21_default: i2c21_default { + spi22_dyn_sleep: spi22_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + spi24_dyn_default: spi24_dyn_default { group1 { - psels = , - ; - bias-pull-up; + psels = , + , + ; }; }; - i2c21_sleep: i2c21_sleep { + spi24_dyn_sleep: spi24_dyn_sleep { group1 { - psels = , - ; - low-power-enable; + psels = , + , + ; + }; + }; + + uart30_dyn_default: uart30_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart30_dyn_sleep: uart30_dyn_sleep { + group1 { + psels = , + ; }; }; }; &i2c21 { - clock-frequency = ; - pinctrl-0 = <&i2c21_default>; - pinctrl-1 = <&i2c21_sleep>; + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c21_dyn_default>; + pinctrl-1 = <&i2c21_dyn_sleep>; pinctrl-names = "default", "sleep"; +}; + +&i2c23 { status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c23_dyn_default>; + pinctrl-1 = <&i2c23_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi22 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi22_dyn_default>; + pinctrl-1 = <&spi22_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi24 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi24_dyn_default>; + pinctrl-1 = <&spi24_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart30 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart30_dyn_default>; + pinctrl-1 = <&uart30_dyn_sleep>; + pinctrl-names = "default", "sleep"; }; diff --git a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/circuitpython.toml index 83e6bcd39c4..c302839366a 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/circuitpython.toml @@ -1 +1,115 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2", "signed.bin"] + +# Headers silkscreened PORT0 (P0.00-P0.09), PORT1:00-15 (P1.00-P1.15), +# PORT1:16-31 (P1.16-P1.31), PORT2 (P2.00-P2.10) and PORT3 (P3.00-P3.12), +# resolved to CSP98 ball names through the package map selected in board.conf. + +[pins] +PORT0_00 = "K3" +PORT0_01 = "K2" +PORT0_02 = "J3" +PORT0_03 = "J2" +PORT0_04 = "J1" +PORT0_05 = "H3" +PORT0_06 = "H2" +PORT0_07 = "G3" +PORT0_08 = "F3" +PORT0_09 = "E3" + +# P1.01 (NFC1) and P1.02 (NFC2) are routed to the NFC antenna by default; +# move R33/R34 to R3/R4 and disable the NFCT peripheral to use them as GPIO. +PORT1_00 = "K8" +PORT1_01 = "J6" +PORT1_02 = "J7" +PORT1_03 = "H9" +PORT1_04 = "J9" +PORT1_05 = "H8" +PORT1_06 = "J8" +PORT1_07 = "G8" +PORT1_08 = "F8" +PORT1_09 = "E8" +PORT1_10 = "D5" +PORT1_11 = "C5" +PORT1_12 = "C4" +PORT1_13 = "C3" +PORT1_14 = "C2" +PORT1_15 = "B2" +PORT1_16 = "B3" +PORT1_17 = "B4" +PORT1_18 = "B5" +PORT1_19 = "B6" +# P1.20/P1.21 feed the 32.768 kHz crystal by default; short SB3/SB4 (and cut +# SB1/SB2) to use them as GPIO. +PORT1_22 = "D6" +PORT1_23 = "B7" +PORT1_24 = "D7" +PORT1_25 = "E7" +PORT1_26 = "B8" +PORT1_27 = "C8" +PORT1_28 = "D8" +PORT1_29 = "H10" +PORT1_30 = "J10" +PORT1_31 = "K9" + +# P2.00-P2.05 are shared with the MX25R64 external flash. +PORT2_00 = "B10" +PORT2_01 = "C10" +PORT2_02 = "D10" +PORT2_03 = "E10" +PORT2_04 = "F10" +PORT2_05 = "G9" +PORT2_06 = "B9" +PORT2_07 = "C9" +PORT2_08 = "D9" +PORT2_09 = "E9" +PORT2_10 = "F9" + +PORT3_00 = "H7" +PORT3_01 = "H6" +PORT3_02 = "H5" +PORT3_03 = "H4" +PORT3_04 = "J4" +PORT3_05 = "G7" +PORT3_06 = "G6" +PORT3_07 = "G5" +PORT3_08 = "G4" +PORT3_09 = "F7" +PORT3_10 = "F4" +PORT3_11 = "E4" +PORT3_12 = "D4" + +# Silkscreen button names. LED0-LED3, SW0-SW3 and PUSH_BUTTON_0-3 already come +# from the devicetree. +BUTTON0 = "B8" +BUTTON1 = "E8" +BUTTON2 = "F8" +BUTTON3 = "H3" + +# Nordic expansion board header (P17), named per the board silkscreen. +# Positions key the gpio-map order (header GPIO 00-21, odd/even columns from +# the A0/B0 end); I0 sits opposite the header's mid GND and has no gpio-map +# entry. Function groups check out: B0 = PWM (P3.04), D0-D3 = SPI +# (P3.00-P3.03), H0-H5 = QSPI (P2.00-P2.05). +[connectors.nordic_expansion_header] +0 = "A0" +1 = "B0" +2 = "C0" +3 = "C1" +4 = "C2" +5 = "C3" +6 = "D0" +7 = "D1" +8 = "E2" +9 = "E1" +10 = "D2" +11 = "D3" +12 = "F0" +13 = "F1" +14 = "C4" +15 = "G0" +16 = "H0" +17 = "H1" +18 = "H2" +19 = "H3" +20 = "H4" +21 = "H5" diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index 6b7b93cce5c..77f26df08db 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = false # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true # Zephyr networking enabled +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/board.conf b/ports/zephyr-cp/boards/nordic/nrf7002dk/board.conf index 346dcf5e7e2..6e0188bd976 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/board.conf +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/board.conf @@ -6,6 +6,10 @@ CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y CONFIG_LOG=n CONFIG_ASSERT=n +# The app slot cannot spare flash headroom (slot0 fills the internal flash +# after the 68 KB boot partition): compile the wpa_supplicant debug print +# machinery out as well. +CONFIG_WIFI_NM_WPA_SUPPLICANT_NO_DEBUG=y CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_BT=n # Settings only stores BT bond keys here. With BT off, its NVS backend would diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay index cec4fbb2b98..0103b74e9e6 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay @@ -1,16 +1,62 @@ -/* Remove slot1 (OTA) and storage, expand slot0 to use the space. - * CircuitPython doesn't use OTA updates, and nothing here uses the settings - * NVS: CONFIG_BT=n, and CIRCUITPY lives on the external mx25r64. */ -&slot0_partition { - /* Shrink mcuboot to 48KB, absorb slot1 and storage, run to end of flash */ - reg = <0x0000C000 0x000F4000>; +// Enable free serial peripheral instances so CircuitPython can mux them to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 0 is left alone: uart0 is the console and i2c0/spi0 share that +// same peripheral, so they must not be enabled. Instance 1 stays with the +// fixed arduino header i2c1, and spi4 stays fixed because it owns the +// MX25R64 flash. The disabled arduino spi3/uart1 nodes keep their devicetree +// pinout in case a shield wants them. + +&pinctrl { + i2c2_dyn_default: i2c2_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c2_dyn_sleep: i2c2_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + uart3_dyn_default: uart3_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart3_dyn_sleep: uart3_dyn_sleep { + group1 { + psels = , + ; + }; + }; }; -&boot_partition { - reg = <0x00000000 0x0000C000>; +&i2c2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c2_dyn_default>; + pinctrl-1 = <&i2c2_dyn_sleep>; + pinctrl-names = "default", "sleep"; }; -/delete-node/ &slot1_partition; -/delete-node/ &storage_partition; +&uart3 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart3_dyn_default>; + pinctrl-1 = <&uart3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml index badaffb8e1b..fb55daaa181 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/circuitpython.toml @@ -1,4 +1,4 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] USB_VID=0x239A USB_PID=0x8168 BLOBS=["nrf_wifi"] diff --git a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml index c6c217056b7..8a7c6b5c2d6 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml @@ -13,7 +13,7 @@ adafruit_pixelbuf = false aesio = true alarm = false analogbufio = false -analogio = false +analogio = true # Zephyr board has analogio atexit = false audiobusio = true # Zephyr board has audiobusio audiocore = true # Zephyr board has audiobusio @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/board.conf b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/board.conf index 61f2d18ca3c..311648c6154 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/board.conf +++ b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/board.conf @@ -1 +1,7 @@ CONFIG_DMA_TCD_QUEUE_SIZE=4 + +# analogio: the board overlay enables lpadc0 (with its vref regulator) and +# dac0; iobroker resolves SoC pads to their fixed LPADC channel and the +# LPDAC output pad. +CONFIG_ADC=y +CONFIG_DAC=y diff --git a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/board.overlay b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/board.overlay index 3eef280f220..ae9febdb29f 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/board.overlay +++ b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/board.overlay @@ -1,23 +1,25 @@ -&w25q64jvssiq { - /delete-node/ partitions; - partitions { - #address-cells = <1>; - #size-cells = <1>; - ranges; +#include "../../../app.overlay" - storage_partition: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x0 DT_SIZE_K(32)>; - }; +/* analogio: enable LPADC0 (with its vref regulator) and the LPDAC0. + * The SoC pads' analog functions are bonded at mux 0 (see the pin functions + * table in modules/hal/nxp/dts/nxp/mcx/MCXN947VDF-pinctrl.h); iobroker + * resolves pads to their fixed LPADC/LPDAC channels. + * + * analogio measures inputs against the SoC's supply-referenced range, so + * mux CFG[REFSEL] to VDDA (3.3 V) instead of the on-die VREF regulator + * (1.8 V full scale): board signals (e.g. the Arduino analog pins) run to + * 3.3 V. VREFH can't be used instead: the schematic wires it to the VREF + * regulator output through 0-ohm R171, so it is 1.8 V like REFSEL = 1. */ +&vref { + status = "okay"; +}; - circuitpy_partition: partition@8000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x00008000 (DT_SIZE_M(8) - DT_SIZE_K(32))>; - }; - }; +&lpadc0 { + status = "okay"; + voltage-ref = <2>; }; -#include "../../../app.overlay" +&dac0 { + status = "okay"; +}; diff --git a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/circuitpython.toml b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/circuitpython.toml index 3272dd4c5f3..966dc2ae634 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/circuitpython.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/circuitpython.toml @@ -1 +1,18 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] + +[pins] +# Arduino header J4's analog pins, from UM12018 Table 20. A0 (ANA_0) and +# A1 (ANA_1) sit on analog-only pads, which only the package pin map can +# name; A2..A5 are GPIO pads the devicetree's arduino-header gpio-map +# already names. +A0 = "P3" +A1 = "R3" + +[connectors.arduino_header] +# The generic arduino-header-r3 name list assumes the gpio-map enumerates +# the whole header; this board only maps its four analog pins (J4 pins 6, +# 8, 10 and 12), so rebind them to their silkscreen names. +"0" = "A2" +"1" = "A3" +"2" = "A4" +"3" = "A5" diff --git a/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml index dfe32e2da1f..91c03d73aa6 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true # Zephyr networking enabled +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/nxp/frdm_rw612/board.overlay b/ports/zephyr-cp/boards/nxp/frdm_rw612/board.overlay index 9153fd433ab..cb054681fa5 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_rw612/board.overlay +++ b/ports/zephyr-cp/boards/nxp/frdm_rw612/board.overlay @@ -1,17 +1,2 @@ -&w25q512jvfiq { - partitions { - /delete-node/ partition@620000; - storage_partition: partition@620000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x00620000 DT_SIZE_K(32)>; - }; - circuitpy_partition: partition@628000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x00628000 (DT_SIZE_M(58) - DT_SIZE_K(128) - DT_SIZE_K(32))>; - }; - }; -}; #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/nxp/frdm_rw612/circuitpython.toml b/ports/zephyr-cp/boards/nxp/frdm_rw612/circuitpython.toml index 9bceea470ca..dc00f11fef3 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_rw612/circuitpython.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_rw612/circuitpython.toml @@ -1,4 +1,4 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2", "signed.bin"] BLOBS=["hal_nxp"] [blob_fetch_args] diff --git a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml index e82b015f969..0fc1d02fb9f 100644 --- a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/board.overlay b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/board.overlay index cd5f0089494..0d0f7420cad 100644 --- a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/board.overlay +++ b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/board.overlay @@ -1,18 +1,3 @@ -&is25wp128 { - partitions { - /delete-node/ partition@e20000; - storage_partition: partition@e20000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x00e20000 DT_SIZE_K(32)>; - }; - circuitpy_partition: partition@e28000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x00e28000 (DT_SIZE_M(2) - DT_SIZE_K(128) - DT_SIZE_K(32))>; - }; - }; -}; &sai1 { mclk-output; diff --git a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/circuitpython.toml b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/circuitpython.toml index 3272dd4c5f3..40562364cde 100644 --- a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/circuitpython.toml @@ -1 +1 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml index ed813ea69c3..133f753c715 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true # Zephyr networking enabled +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.overlay b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.overlay index 436cfc83276..aaaa51c2e0e 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.overlay @@ -1,33 +1,3 @@ -&flash0 { - partitions { - #address-cells = <1>; - #size-cells = <1>; - - code_partition: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "code-partition"; - reg = <0x0 0x17f000>; - read-only; - }; - - storage_partition: partition@17f000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x17f000 0x1000>; - }; - - nvm_partition: partition@180000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0x180000 0x1000>; - }; - - circuitpy_partition: partition@181000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x181000 (DT_SIZE_M(4) - 0x181000)>; - }; - }; -}; - +/* Partition layout is owned by the mcuboot fork (dts/raspberrypi/rpi_pico2.dtsi) + * and applied to this image by sysbuild. This overlay only adds board/app config. */ #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/circuitpython.toml index c042e1581c0..bd0e3ca07ca 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/circuitpython.toml @@ -1,4 +1,4 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf", "uf2"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] BLOBS=["hal_infineon"] # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml index 9a4fd0543fa..87a99062530 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/board.overlay b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/board.overlay index a8ce944ab8a..aaaa51c2e0e 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/board.overlay @@ -1,33 +1,3 @@ -&flash0 { - partitions { - #address-cells = <1>; - #size-cells = <1>; - - code_partition: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "code-partition"; - reg = <0x0 0xfe000>; - read-only; - }; - - storage_partition: partition@fe000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0xfe000 0x1000>; - }; - - nvm_partition: partition@ff000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0xff000 0x1000>; - }; - - circuitpy_partition: partition@100000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x100000 (DT_SIZE_M(4) - 0x100000)>; - }; - }; -}; - +/* Partition layout is owned by the mcuboot fork (dts/raspberrypi/rpi_pico2.dtsi) + * and applied to this image by sysbuild. This overlay only adds board/app config. */ #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/circuitpython.toml index 75ceb6ba60d..75da8760014 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/circuitpython.toml @@ -1,4 +1,4 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf", "uf2"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "raspberrypi/raspberry_pi_pico2" diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml index 2cad2417bfa..321aa7a01f6 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true # Zephyr networking enabled +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.overlay b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.overlay index 3130d8dc5aa..f8f00f5cb37 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.overlay @@ -1,44 +1,3 @@ -&flash0 { - /delete-node/ partitions; - - partitions { - ranges; - #address-cells = <1>; - #size-cells = <1>; - - /* Reserved memory for the second stage bootloader */ - second_stage_bootloader: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "second_stage_bootloader"; - reg = <0x00000000 0x100>; - read-only; - }; - - code_partition: partition@100 { - compatible = "zephyr,mapped-partition"; - label = "code-partition"; - reg = <0x100 (0x17f000 - 0x100)>; - read-only; - }; - - storage_partition: partition@17f000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x17f000 0x1000>; - }; - - nvm_partition: partition@180000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0x180000 0x1000>; - }; - - circuitpy_partition: partition@181000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x181000 (DT_SIZE_M(2) - 0x181000)>; - }; - }; -}; - +/* Partition layout is owned by the mcuboot fork (dts/raspberrypi/rpi_pico.dtsi) + * and applied to this image by sysbuild. This overlay only adds board/app config. */ #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/circuitpython.toml index deb99e3effa..4b5d2df0800 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/circuitpython.toml @@ -1,4 +1,4 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf", "uf2"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] BLOBS=["hal_infineon"] # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml index 442978a5cfe..0528897b8df 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = true # Zephyr board has nvm +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/board.overlay b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/board.overlay index bfd690085a7..f8f00f5cb37 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/board.overlay @@ -1,44 +1,3 @@ -&flash0 { - /delete-node/ partitions; - - partitions { - ranges; - #address-cells = <1>; - #size-cells = <1>; - - /* Reserved memory for the second stage bootloader */ - second_stage_bootloader: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "second_stage_bootloader"; - reg = <0x00000000 0x100>; - read-only; - }; - - code_partition: partition@100 { - compatible = "zephyr,mapped-partition"; - label = "code-partition"; - reg = <0x100 (0xfe000 - 0x100)>; - read-only; - }; - - storage_partition: partition@fe000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0xfe000 0x1000>; - }; - - nvm_partition: partition@ff000 { - compatible = "zephyr,mapped-partition"; - label = "nvm"; - reg = <0xff000 0x1000>; - }; - - circuitpy_partition: partition@100000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x100000 (DT_SIZE_M(2) - 0x100000)>; - }; - }; -}; - +/* Partition layout is owned by the mcuboot fork (dts/raspberrypi/rpi_pico.dtsi) + * and applied to this image by sysbuild. This overlay only adds board/app config. */ #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/circuitpython.toml index fae9877df08..6de00993861 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/circuitpython.toml @@ -1,4 +1,4 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf", "uf2"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "raspberrypi/raspberry_pi_pico" diff --git a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml index e51c13c1ee6..0a499fa14e4 100644 --- a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/board.overlay b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/board.overlay index 6a8343384c9..cb054681fa5 100644 --- a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/board.overlay +++ b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/board.overlay @@ -1,19 +1,2 @@ -/* Remove slot1 (OTA), expand slot0 to use the space. - * CircuitPython doesn't use OTA updates. */ -&slot0_partition { - reg = <0x00010000 DT_SIZE_K(1024)>; -}; - -/delete-node/ &slot1_partition; - -&flash0 { - partitions{ - circuitpy_partition: partition@118000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x118000 (DT_SIZE_M(4) - DT_SIZE_K(1120))>; - }; - }; -}; #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/circuitpython.toml b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/circuitpython.toml index f7fad6bc444..60a5b67efb9 100644 --- a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/circuitpython.toml +++ b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/circuitpython.toml @@ -1,2 +1,2 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2", "signed.bin"] BLOBS=["hal_renesas"] diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index 710f7387fe3..e8f4941f7bb 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/tests/feature_check/async_check.py.exp b/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.conf similarity index 100% rename from tests/feature_check/async_check.py.exp rename to ports/zephyr-cp/boards/renesas/ek_ra6m5/board.conf diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.overlay b/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.overlay new file mode 100644 index 00000000000..32bf713e4e5 --- /dev/null +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.overlay @@ -0,0 +1,22 @@ +/* This board boots via the Adaboot MCUboot fork. Its + * bootloader/mcuboot/dts/renesas/ek_ra6m5.dtsi owns the partition layout + * (boot, slot0 and storage on the internal code flash, nvm on the data + * flash) and is applied to both the bootloader and the application image + * by sysbuild, so no partition definitions belong here. CIRCUITPY gets the + * fatfs_partition the layout carves from the 32 MB QSPI NOR (mx25l25645g). + * + * Disable OFS (Option Function Select) nodes so their high-address + * sections don't end up in the app HEX. MCUboot's own build still has + * them, so the merged HEX programs OFS correctly. + */ +&option_setting_ofs0 { status = "disabled"; }; +&option_setting_dualsel { status = "disabled"; }; +&option_setting_ofs1_sec { status = "disabled"; }; +&option_setting_banksel_sec { status = "disabled"; }; +&option_setting_bps_sec { status = "disabled"; }; +&option_setting_pbps_sec { status = "disabled"; }; +&option_setting_ofs1_sel { status = "disabled"; }; +&option_setting_banksel_sel { status = "disabled"; }; +&option_setting_bps_sel { status = "disabled"; }; + +#include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/circuitpython.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/circuitpython.toml index 13ff40aa042..a741a9bfbdf 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/circuitpython.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/circuitpython.toml @@ -1,3 +1,3 @@ # TX is on pin P411. RX is on pin P410. -CIRCUITPY_BUILD_EXTENSIONS = ["elf"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index 700960cfcab..103da8c4056 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -13,7 +13,7 @@ adafruit_pixelbuf = false aesio = true alarm = false analogbufio = false -analogio = false +analogio = true # Zephyr board has analogio atexit = false audiobusio = false audiocore = false @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/board.conf b/ports/zephyr-cp/boards/renesas/ek_ra8d1/board.conf index 451f4199116..19b8c318ae4 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/board.conf +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/board.conf @@ -10,3 +10,9 @@ CONFIG_USBD_MAX_UDC_MSG=32 CONFIG_USBD_MSG_SLAB_COUNT=32 CONFIG_UDC_BUF_COUNT=32 CONFIG_UDC_BUF_POOL_SIZE=2048 + +# ADC0 is on the mikroBUS AN pin (P0.04); analogio resolves pads to their +# fixed channels through iobroker. The board dts also enables dac0, whose +# fixed-per-SoC output pad (P0.14) iobroker applies analog enable on. +CONFIG_ADC=y +CONFIG_DAC=y diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/board.overlay b/ports/zephyr-cp/boards/renesas/ek_ra8d1/board.overlay index 416be7b510e..68296269ecf 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/board.overlay +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/board.overlay @@ -1,5 +1,17 @@ -&s28hl512t { - /delete-node/ partitions; -}; + +/* Disable OFS (Option Function Select) nodes so their high-address + * sections don't end up in the app HEX. MCUboot's own build still + * has them, so the merged HEX programs OFS correctly. + */ +&option_setting_ofs0 { status = "disabled"; }; +&option_setting_ofs2 { status = "disabled"; }; +&option_setting_dualsel { status = "disabled"; }; +&option_setting_ofs1_sec { status = "disabled"; }; +&option_setting_banksel_sec { status = "disabled"; }; +&option_setting_bps_sec { status = "disabled"; }; +&option_setting_pbps_sec { status = "disabled"; }; +&option_setting_ofs1_sel { status = "disabled"; }; +&option_setting_banksel_sel { status = "disabled"; }; +&option_setting_bps_sel { status = "disabled"; }; #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/circuitpython.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/circuitpython.toml index 0e19d8d7157..21b7c923ebe 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/circuitpython.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/circuitpython.toml @@ -1,2 +1,2 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["elf"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] SHIELDS = ["rtkmipilcdb00000be"] diff --git a/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/autogen_board_info.toml b/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/autogen_board_info.toml index 7f56bc19de7..13a161d21ed 100644 --- a/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true # Zephyr networking enabled +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/board.overlay b/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/board.overlay index f345c30b9b0..5eadcac6db5 100644 --- a/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/board.overlay +++ b/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/board.overlay @@ -3,30 +3,6 @@ // falls back to the Zephyr console on &ulpuart, reaching the host over the // on-board J-Link VCOM. -// CIRCUITPY filesystem. -// -// supervisor/flash.c needs a partition labeled `circuitpy_partition`, and the -// whole 8 MB is already partitioned by siwg917m111mgtba.dtsi with no -// unallocated space to claim. The upper region is owned by the network -// processor, which refuses M4 writes there, so CIRCUITPY is carved out of the -// M4's own code_partition, shrunk from 2008K to 1M. Overflowing that fails at -// link time rather than corrupting the filesystem. -&flash0 { - partitions { - code_partition: partition@202000 { - compatible = "zephyr,mapped-partition"; - label = "code_partition"; - reg = <0x00202000 0x00100000>; - }; - - circuitpy_partition: partition@302000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x00302000 0x000f6000>; - }; - }; -}; - // GSPI on the breakout pads. The board .dts leaves spi0 disabled and wires // nothing, so the pin assignment here is taken from the mapping Silicon Labs // ships for this board in diff --git a/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/circuitpython.toml b/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/circuitpython.toml index 1f15a6f8349..e5296cae130 100644 --- a/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/circuitpython.toml +++ b/ports/zephyr-cp/boards/silabs/siwx917_dk2605a/circuitpython.toml @@ -1 +1 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["rps"] +CIRCUITPY_BUILD_EXTENSIONS = ["rps", "hex"] diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml index f2b7a9c78b7..0ea5e7a159a 100644 --- a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.conf b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.conf new file mode 100644 index 00000000000..721df88a7fc --- /dev/null +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.conf @@ -0,0 +1,7 @@ +# The N6 has no internal flash and the upstream board defconfig sets XIP=n, +# linking the whole image into the RAMLOAD region, which is too small for +# CircuitPython ("Image is too large to fit RAMLOAD configuration"). The +# mcuboot fork's dts/st/nucleo_n657x0_q.dtsi points zephyr,flash at the +# memory-mapped XSPI NOR, so execute in place from the slot0 partition +# instead. +CONFIG_XIP=y diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.overlay b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.overlay new file mode 100644 index 00000000000..05c5775d9ac --- /dev/null +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.overlay @@ -0,0 +1,8 @@ +/* The USB device controller (zephyr_udc0), the XIP code flash (xip_flash) + * with its partition map, and the flash chosen properties are defined in the + * mcuboot fork's dts/st/nucleo_n657x0_q.dtsi, which sysbuild prepends to this + * image's overlays. app-partition.overlay then points + * zephyr,code-partition at slot0_partition. app.overlay only adds the CDC + * ACM console/data endpoints and the HID device. + */ +#include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/circuitpython.toml b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/circuitpython.toml index e8de92166db..87123c491f7 100644 --- a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/circuitpython.toml +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/circuitpython.toml @@ -1,4 +1,4 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] # This part executes from RAM and is already at 93% of it, so it cannot # absorb the roughly 90 KB ulab adds. diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index 8f32deab204..18e94ec03f4 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/circuitpython.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/circuitpython.toml index 3ca45dd798b..3e307eac332 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/circuitpython.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/circuitpython.toml @@ -1,3 +1,3 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] USB_VID=0x239A USB_PID=0x816A diff --git a/ports/zephyr-cp/boards/st/stm32h750b_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h750b_dk/autogen_board_info.toml index 7f7146c8e10..1bbbc4c5ae5 100644 --- a/ports/zephyr-cp/boards/st/stm32h750b_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h750b_dk/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false @@ -110,8 +112,8 @@ traceback = true uheap = false usb = false usb_audio = false -usb_cdc = false -usb_hid = false +usb_cdc = true +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/st/stm32h750b_dk/board.overlay b/ports/zephyr-cp/boards/st/stm32h750b_dk/board.overlay index 7e038bb77aa..96ba7e05bac 100644 --- a/ports/zephyr-cp/boards/st/stm32h750b_dk/board.overlay +++ b/ports/zephyr-cp/boards/st/stm32h750b_dk/board.overlay @@ -1,21 +1,10 @@ -&ext_flash { - partitions { - /delete-node/ partition@7800000; - - storage_partition: partition@7800000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x7800000 DT_SIZE_K(32)>; - }; - - circuitpy_partition: partition@7808000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x7808000 (DT_SIZE_M(8) - DT_SIZE_K(32))>; - }; - }; -}; - &rng { status = "okay"; }; + +/* The USB device controller (zephyr_udc0) and the QSPI flash partition map + * are defined in the mcuboot fork's dts/st/stm32h750b_dk.dtsi, which + * sysbuild prepends to this image's overlays. app.overlay only adds the + * CDC ACM console/data endpoints. + */ +#include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/st/stm32h750b_dk/circuitpython.toml b/ports/zephyr-cp/boards/st/stm32h750b_dk/circuitpython.toml index 83e6bcd39c4..40562364cde 100644 --- a/ports/zephyr-cp/boards/st/stm32h750b_dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/st/stm32h750b_dk/circuitpython.toml @@ -1 +1 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index 666515bca60..40c069d57fb 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/board.overlay b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/board.overlay index 75e497714c9..ff27e56410d 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/board.overlay +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/board.overlay @@ -1,22 +1,3 @@ -&mx25lm51245 { - /delete-node/ partitions; - - partitions { - compatible = "fixed-partitions"; - #address-cells = <1>; - #size-cells = <1>; - - storage_partition: partition@0 { - label = "storage"; - reg = <0x00000000 DT_SIZE_K(32)>; - }; - - circuitpy_partition: partition@8000 { - label = "circuitpy"; - reg = <0x00008000 (DT_SIZE_M(64) - DT_SIZE_K(32))>; - }; - }; -}; &sram5 { status = "disabled"; diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/circuitpython.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/circuitpython.toml index 83e6bcd39c4..40562364cde 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/circuitpython.toml @@ -1 +1 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] diff --git a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml index 84d17af7c12..9e1707923b3 100644 --- a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml @@ -53,7 +53,9 @@ frequencyio = false getpass = true gifio = true # Zephyr board has busio gnss = false +hardwarekey = false hashlib = true +hmac = false hostnetwork = false i2cdisplaybus = true # Zephyr board has busio i2cioexpander = false @@ -76,7 +78,7 @@ microcontroller = true mipidsi = false msgpack = true neopixel_write = false -nvm = false +nvm = true # Board has NVM partition onewireio = false os = true paralleldisplaybus = false diff --git a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/board.overlay b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/board.overlay index da2c7427c4a..ceaa72a311c 100644 --- a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/board.overlay +++ b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/board.overlay @@ -1,66 +1,21 @@ -&flash0 { - /delete-node/ partitions; - - partitions { - ranges; - #address-cells = <1>; - #size-cells = <1>; - - boot_partition: partition@0 { - compatible = "zephyr,mapped-partition"; - label = "mcuboot"; - reg = <0x00000000 DT_SIZE_K(64)>; - }; - - slot0_partition: partition@10000 { - compatible = "zephyr,mapped-partition"; - label = "image-0"; - reg = <0x00010000 DT_SIZE_K(928)>; - }; - - storage_partition: partition@f80000 { - compatible = "zephyr,mapped-partition"; - label = "storage"; - reg = <0x001e0000 DT_SIZE_K(64)>; - }; - - circuitpy_partition: partition@108000 { - compatible = "zephyr,mapped-partition"; - label = "circuitpy"; - reg = <0x00108000 DT_SIZE_K(992)>; - }; - }; -}; &rng { status = "okay"; }; -/* - * USB on STM32WBA requires VOS Range 1. Zephyr selects VOS from HCLK, and - * 16 MHz keeps it in Range 2, which trips an assertion in udc_stm32. - * Run SYSCLK from full 32 MHz HSE so VOS is set to Range 1. - */ -&clk_hse { - /delete-property/ hse-div2; +/* The board's &rtc node is already status-okay in the upstream devicetree, so + * alias it so the rtc module picks it up like other RTC boards do. */ +/ { + aliases { + rtc = &rtc; + }; }; -&rcc { - clock-frequency = ; - ahb5-prescaler = <1>; -}; - -zephyr_udc0: &usbotg_hs { - status = "okay"; -}; - -&otghs_phy { - /* OTG HS clock source is 32 MHz HSE */ - clocks = <&rcc STM32_CLOCK(AHB2, 15)>, - <&rcc STM32_SRC_HSE OTGHS_SEL(0)>; - clock-reference = "SYSCFG_OTG_HS_PHY_CLK_32MHz"; - status = "okay"; -}; +/* The USB device controller (zephyr_udc0, including the OTG HS PHY and the + * HSE/VOS clocking it needs) and the joystick-selector boot button are + * defined in the mcuboot fork's dts/st/stm32wba65i_dk1.dtsi, which sysbuild + * prepends to this image's overlays so both images share the definitions. + */ &bt_hci_wba { /* Use HASH IRQ to allocate Radio SW Low Process (Warning HASH is not used) */ diff --git a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/circuitpython.toml b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/circuitpython.toml index fb3dfea6867..44c6b374674 100644 --- a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/circuitpython.toml +++ b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/circuitpython.toml @@ -1,2 +1,2 @@ -CIRCUITPY_BUILD_EXTENSIONS = ["hex"] +CIRCUITPY_BUILD_EXTENSIONS = ["hex", "uf2"] BLOBS=["hal_stm32"] diff --git a/ports/zephyr-cp/common-hal/_bleio/Adapter.c b/ports/zephyr-cp/common-hal/_bleio/Adapter.c index c3684a3b148..7819e68676e 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Adapter.c +++ b/ports/zephyr-cp/common-hal/_bleio/Adapter.c @@ -56,7 +56,10 @@ static bool ble_advertising = false; // than user code. Lets the workflow restart its own adverts without disturbing // user-initiated advertising. static bool ble_advertising_internal = false; -static bool ble_adapter_enabled = true; +// Set to true when common_hal_bleio_adapter_set_enabled(true) brings the stack up. +// set_enabled(false) clears this flag and stops advertising and scanning, but +// the controller keeps running. +static bool ble_adapter_enabled = false; #define BLEIO_ADV_MAX_FIELDS 16 #define BLEIO_ADV_MAX_DATA_LEN 31 @@ -149,6 +152,7 @@ static void bleio_connection_clear(bleio_connection_internal_t *self) { self->connection_obj = mp_const_none; self->pair_status = PAIR_NOT_PAIRED; self->sec_err = 0; + self->user_owned = false; } static void bleio_connection_release(bleio_connection_internal_t *connection, uint8_t reason) { @@ -205,6 +209,11 @@ static void bleio_connected_cb(struct bt_conn *conn, uint8_t err) { int mtu_err = bt_gatt_exchange_mtu(conn, &mtu_exchange_params[idx]); (void)mtu_err; + // A peripheral connection belongs to whoever started the advertising it + // answered: the BLE workflow (internal) or user code. Central connections + // are marked in common_hal_bleio_adapter_connect(). + connection->user_owned = !ble_advertising_internal; + // When connectable advertising results in a connection, the controller // auto-stops advertising. Clear our flag to match (we cannot call // stop_advertising() here because this callback runs in Zephyr's BT @@ -330,6 +339,32 @@ static uint16_t bleio_validate_and_convert_timeout(mp_float_t timeout) { return (uint16_t)timeout_units; } +// Start the Zephyr Bluetooth host and load its settings (identity, bond keys, +// CCC state) if that hasn't happened yet. Returns 0 or a negative Zephyr errno. +// +// This is separate from set_enabled() because the BLE workflow asks about +// bonds (is_bonded_to_central(), erase_bonding()) in supervisor_bluetooth_init(), +// before it enables the adapter in supervisor_start_bluetooth(). Bond keys live +// in the settings subsystem and are only in RAM after settings_load(), so those +// calls must be able to bring the stack up on their own. Nothing here powers the +// controller down later: set_enabled(false) leaves it running. +static int bleio_adapter_ensure_stack_ready(void) { + if (bt_is_ready()) { + return 0; + } + int err = bt_enable(NULL); + if (err != 0 && err != -EALREADY) { + return err; + } + + // bt_init() returns early without setting BT_DEV_READY when + // CONFIG_BT_SETTINGS=y and no identity is loaded yet. + // Load settings so the BT settings handler fires and calls + // bt_finalize_init() which sets BT_DEV_READY. + settings_load(); + return 0; +} + void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enabled) { if (enabled == ble_adapter_enabled) { return; @@ -338,17 +373,9 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { bleio_connection_clear(&bleio_connections[i]); } - if (!bt_is_ready()) { - int err = bt_enable(NULL); - if (err != 0 && err != -EALREADY) { - raise_zephyr_error(err); - } - - // bt_init() returns early without setting BT_DEV_READY when - // CONFIG_BT_SETTINGS=y and no identity is loaded yet. - // Load settings so the BT settings handler fires and calls - // bt_finalize_init() which sets BT_DEV_READY. - settings_load(); + int err = bleio_adapter_ensure_stack_ready(); + if (err != 0) { + raise_zephyr_error(err); } // Ensure a local identity exists so advertising/connections work and the // name is stable across reboots. bt_id_create persists the identity when @@ -815,6 +842,9 @@ mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self, bleio_addre // ref via bleio_connection_track(). Drop the create ref now. bt_conn_unref(conn); + // Only user code connects in the central role. + connection->user_owned = true; + self->connection_objs = NULL; return bleio_connection_new_from_internal(connection); } @@ -840,6 +870,16 @@ static void bond_iterator_check(const struct bt_bond_info *info, void *user_data } void common_hal_bleio_adapter_erase_bonding(bleio_adapter_obj_t *self) { + // Can be called from Python as _bleio.adapter.erase_bonding(), and from + // supervisor_bluetooth_init() on a discovery-mode boot, before the adapter is + // enabled. Bond keys are only visible once the stack is up and settings are + // loaded; without this the loop below finds nothing and the bond reappears + // when the workflow starts. The workflow call runs before the VM, so a failure + // to start the stack is ignored here rather than raised. + if (bleio_adapter_ensure_stack_ready() != 0) { + return; + } + // Unpair all bonded devices for all local identities. for (uint8_t id = 0; id < CONFIG_BT_ID_MAX; id++) { // bt_unpair takes an addr; use bt_foreach_bond to iterate and unpair. @@ -866,7 +906,14 @@ void common_hal_bleio_adapter_erase_bonding(bleio_adapter_obj_t *self) { } bool common_hal_bleio_adapter_is_bonded_to_central(bleio_adapter_obj_t *self) { - // Check if any bond exists for identity 0 + // Called only by the BLE workflow, from supervisor_bluetooth_init() before + // the adapter is enabled and from supervisor_bluetooth_background(); see + // erase_bonding() above. Runs before the VM, so don't raise. + if (bleio_adapter_ensure_stack_ready() != 0) { + return false; + } + + // Check if any bond exists for any local identity. for (uint8_t id = 0; id < CONFIG_BT_ID_MAX; id++) { bool has_bonds = false; bt_foreach_bond(id, bond_iterator_check, &has_bonds); @@ -882,36 +929,6 @@ void bleio_adapter_gc_collect(bleio_adapter_obj_t *adapter) { gc_collect_root((void **)bleio_connections, sizeof(bleio_connections) / sizeof(size_t)); } -void bleio_adapter_reset(bleio_adapter_obj_t *adapter) { - if (adapter == NULL) { - return; - } - - common_hal_bleio_adapter_stop_scan(adapter); - common_hal_bleio_adapter_stop_advertising(adapter); - - for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { - bleio_connection_internal_t *connection = &bleio_connections[i]; - if (connection->conn != NULL) { - bt_conn_disconnect(connection->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); - } - if (connection->connection_obj != MP_OBJ_NULL && - connection->connection_obj != mp_const_none) { - bleio_connection_obj_t *connection_obj = MP_OBJ_TO_PTR(connection->connection_obj); - connection_obj->connection = NULL; - connection_obj->disconnect_reason = BT_HCI_ERR_REMOTE_USER_TERM_CONN; - } - bleio_connection_clear(connection); - } - - adapter->scan_results = NULL; - adapter->connection_objs = NULL; - active_scan_results = NULL; - ble_advertising = false; - ble_advertising_internal = false; - ble_adapter_enabled = bt_is_ready(); -} - bleio_adapter_obj_t *common_hal_bleio_allocate_adapter_or_raise(void) { return &common_hal_bleio_adapter_obj; } diff --git a/ports/zephyr-cp/common-hal/_bleio/Adapter.h b/ports/zephyr-cp/common-hal/_bleio/Adapter.h index 25e1c35d563..ba84e810bfe 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Adapter.h +++ b/ports/zephyr-cp/common-hal/_bleio/Adapter.h @@ -31,7 +31,6 @@ typedef struct { } bleio_adapter_obj_t; void bleio_adapter_gc_collect(bleio_adapter_obj_t *adapter); -void bleio_adapter_reset(bleio_adapter_obj_t *adapter); // Queue a background run of supervisor_bluetooth_background() so the VM drains // incoming BLE PacketBuffer data. Safe to call from Zephyr BT/workqueue context. diff --git a/ports/zephyr-cp/common-hal/_bleio/CharacteristicBuffer.c b/ports/zephyr-cp/common-hal/_bleio/CharacteristicBuffer.c index 9ce48ac97c4..5b96b841d55 100644 --- a/ports/zephyr-cp/common-hal/_bleio/CharacteristicBuffer.c +++ b/ports/zephyr-cp/common-hal/_bleio/CharacteristicBuffer.c @@ -21,6 +21,8 @@ #include "supervisor/shared/tick.h" +#include "common-hal/_bleio/Adapter.h" + #include "common-hal/_bleio/CharacteristicBuffer.h" #include "common-hal/_bleio/Characteristic.h" @@ -43,6 +45,10 @@ void bleio_characteristic_buffer_extend(bleio_characteristic_buffer_obj_t *self, } else { ring_buf_put(&self->ringbuf, data, len); } + // Wake the main task so it can service the new RX data instead of + // sleeping out its full timeout. This is what makes the "Press any key" + // wait end early when REPL input arrives over the BLE serial service. + bleio_request_bluetooth_background(); } void _common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffer_obj_t *self, diff --git a/ports/zephyr-cp/common-hal/_bleio/Connection.c b/ports/zephyr-cp/common-hal/_bleio/Connection.c index 77942728a5f..12b2a8efe5f 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Connection.c +++ b/ports/zephyr-cp/common-hal/_bleio/Connection.c @@ -577,6 +577,21 @@ bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) { return self->connection->pair_status == PAIR_PAIRED; } +// Authenticated (MITM-protected) pairing - numeric comparison - is implemented on the +// espressif port only. This port can't tell a "Just Works" bond from an authenticated +// one, so report the conservative answer rather than a guarantee it can't make. +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + return false; +} + +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { + return mp_const_none; +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + mp_raise_NotImplementedError(NULL); +} + mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist) { bleio_connection_internal_t *connection = self->connection; if (connection == NULL || connection->conn == NULL) { diff --git a/ports/zephyr-cp/common-hal/_bleio/Connection.h b/ports/zephyr-cp/common-hal/_bleio/Connection.h index 323e9393103..9565fd31b8b 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Connection.h +++ b/ports/zephyr-cp/common-hal/_bleio/Connection.h @@ -24,6 +24,10 @@ typedef struct { mp_obj_t connection_obj; volatile pair_status_t pair_status; uint8_t sec_err; // Security error code from pairing attempt + // True if user code initiated this connection or accepted it with its own + // advertising. bleio_user_reset() disconnects only these; the BLE workflow + // connection is not user-owned and survives VM restarts. + bool user_owned; } bleio_connection_internal_t; typedef struct { diff --git a/ports/zephyr-cp/common-hal/_bleio/PacketBuffer.c b/ports/zephyr-cp/common-hal/_bleio/PacketBuffer.c index 8cdb5fcae76..6e3f42a6729 100644 --- a/ports/zephyr-cp/common-hal/_bleio/PacketBuffer.c +++ b/ports/zephyr-cp/common-hal/_bleio/PacketBuffer.c @@ -134,28 +134,36 @@ static void packet_buffer_send_work_handler(struct k_work *work) { bleio_packet_buffer_obj_t *self = CONTAINER_OF( k_work_delayable_from_work(work), bleio_packet_buffer_obj_t, send_work); - // A notification is awaiting its completion callback; it will resubmit us. - if (self->packet_queued) { - return; - } - if (self->pending_size == 0) { - return; // nothing staged - } - // Server-side notify path only; clients write directly. characteristic is - // NULL after deinit, so bail before touching it (a completion callback can - // resubmit us after teardown). + // NULL after deinit, so bail before touching it (notify_complete_cb() can + // reschedule send_work after teardown). bleio_characteristic_obj_t *c = self->characteristic; if (c == NULL || self->client) { return; } if (!conn_is_valid(self)) { - // Stale connection: drop everything staged. + // The peer is gone: drop everything staged, and clear packet_queued even + // if a notification was handed to bt_gatt_notify_cb() already and its + // notify_complete_cb() has not run. It never will: Zephyr does not call + // the ATT callback for a PDU destroyed by a disconnect (att.c + // att_on_sent_cb(): "Bearer not connected, dropping ATT cb"). If + // packet_queued stayed set, the early return below would skip every + // later send on this buffer, even after the peer reconnects. self->pending_size = 0; self->packet_queued = false; return; } + // A notification has been handed to bt_gatt_notify_cb() and its + // notify_complete_cb() has not run yet. That callback reschedules send_work, + // so this handler will run again once the controller has sent the PDU. + if (self->packet_queued) { + return; + } + if (self->pending_size == 0) { + return; // nothing staged + } + // Staging keeps pending_size <= the negotiated ATT MTU payload, so the // whole staged packet fits in one notification. struct bt_gatt_notify_params params = { @@ -176,27 +184,34 @@ static void packet_buffer_send_work_handler(struct k_work *work) { self->pending_index ^= 1; // VM fills the other buffer next return; } + if (err == -ENOMEM || err == -EAGAIN) { + // No ATT TX buffer right now. The ATT TX pool is shared across all ATT + // traffic on all connections, so it can be full from other + // notifies/indications/responses even when we have nothing in flight. + // Running on the workqueue makes the allocator use K_NO_WAIT: it returns + // NULL and the stack returns -ENOMEM *before* copying or queueing a PDU — + // nothing sent, nothing dropped; the bytes are still in + // outgoing[pending_index]. Leave the data staged and reschedule ourselves + // after a short delay so we retry even when the VM is idle (no write/flush + // to drive us). The delay — not an immediate resubmit — keeps the workqueue + // from busy-looping against a full pool. + k_work_reschedule(&self->send_work, K_MSEC(2)); + return; + } if (err == -ENOTCONN) { - // Peer disconnected — discard everything pending and cancel any - // pending delayed retry. (We're here only when packet_queued is clear, - // so no in-flight completion is owed.) + // Peer disconnected — forget the connection too. (We're here only when + // packet_queued is clear, so no in-flight completion is owed.) self->conn = NULL; - self->pending_size = 0; - self->packet_queued = false; - k_work_cancel_delayable(&self->send_work); - return; } - // -ENOMEM / -EAGAIN: no ATT TX buffer right now. The ATT TX pool is shared - // across all ATT traffic on all connections, so it can be full from other - // notifies/indications/responses even when we have nothing in flight. - // Running on the workqueue makes the allocator use K_NO_WAIT: it returns - // NULL and the stack returns -ENOMEM *before* copying or queueing a PDU — - // nothing sent, nothing dropped; the bytes are still in - // outgoing[pending_index]. Leave the data staged and reschedule ourselves - // after a short delay so we retry even when the VM is idle (no write/flush - // to drive us). The delay — not an immediate resubmit — keeps the workqueue - // from busy-looping against a full pool. - k_work_reschedule(&self->send_work, K_MSEC(2)); + // Any other error is not transient: -ENOTCONN (peer gone), -EPERM (link not + // encrypted and the characteristic requires it), -EINVAL (peer not + // subscribed; CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION), -ENOENT (attribute not + // registered). Retrying would spin forever and wedge this buffer. Drop the + // staged packet, as nordic and espressif do on non-resource errors, and + // cancel any pending delayed retry. + self->pending_size = 0; + self->packet_queued = false; + k_work_cancel_delayable(&self->send_work); } // Shared core for both the Python-facing (allocating) and workflow diff --git a/ports/zephyr-cp/common-hal/_bleio/Service.c b/ports/zephyr-cp/common-hal/_bleio/Service.c index 5f0c7ee268b..4333ee21c25 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Service.c +++ b/ports/zephyr-cp/common-hal/_bleio/Service.c @@ -45,6 +45,82 @@ static const struct bt_uuid_16 _uuid_chrc = BT_UUID_INIT_16(BT_UUID_GATT_CHRC_VA static const struct bt_uuid_16 _uuid_ccc = BT_UUID_INIT_16(BT_UUID_GATT_CCC_VAL); static const struct bt_uuid_16 _uuid_cud = BT_UUID_INIT_16(BT_UUID_GATT_CUD_VAL); +// User-created services live on the GC heap, but once registered, Zephyr's GATT +// database holds a pointer to the bt_gatt_service node inside the object. So a +// registered heap service must stay reachable until it is unregistered, and it +// must be unregistered before the heap it lives in goes away. The zephyr port never +// restarts the Bluetooth stack (set_enabled(false) leaves it running), so the +// database is never cleared for us: bleio_user_reset() unregisters the services at the +// end of every VM run. Same pattern as ports/espressif/common-hal/_bleio/Service.c. +static bleio_service_obj_t *_retained_services; + +static void service_retain(bleio_service_obj_t *self) { + if (!gc_ptr_on_heap((void *)self)) { + // Statically allocated workflow service; the supervisor owns its lifetime. + return; + } + for (bleio_service_obj_t *it = _retained_services; it != NULL; it = it->next_retained) { + if (it == self) { + return; + } + } + self->next_retained = _retained_services; + _retained_services = self; +} + +static void service_release(bleio_service_obj_t *self) { + bleio_service_obj_t **prev = &_retained_services; + for (bleio_service_obj_t *it = *prev; it != NULL; it = it->next_retained) { + if (it == self) { + *prev = it->next_retained; + it->next_retained = NULL; + return; + } + prev = &it->next_retained; + } +} + +// One pointer is enough: the GC traces next_retained through the rest of the chain. +void bleio_service_gc_collect(void) { + gc_collect_ptr(_retained_services); +} + +void bleio_service_unregister_retained(void) { + while (_retained_services != NULL) { + bleio_service_obj_t *service = _retained_services; + // The characteristics' value buffers are on the port heap and nothing + // else frees them once the VM heap is gone. + mp_obj_list_t *list = service->characteristic_list; + if (list != NULL) { + for (size_t i = 0; i < list->len; i++) { + common_hal_bleio_characteristic_deinit(MP_OBJ_TO_PTR(list->items[i])); + } + } + // Unregisters from Zephyr, frees attrs, and removes it from this list. + common_hal_bleio_service_deinit(service); + } +} + +// Zephyr write permission for the CCC descriptor. If reading the characteristic +// value requires encryption or authentication, so does subscribing to it; +// otherwise a client could get the value from notifications without ever being +// made to pair. An unencrypted write then gets Insufficient Encryption or +// Authentication, which is what makes a host pair. Same as espressif (#11236). +// NO_ACCESS on read is not a link-security level, so it leaves the CCC writable; +// nordic differs and makes such a characteristic unsubscribable. +static uint16_t ccc_write_perm(bleio_attribute_security_mode_t read_perm) { + switch (read_perm) { + case SECURITY_MODE_ENC_NO_MITM: + return BT_GATT_PERM_WRITE_ENCRYPT; + case SECURITY_MODE_ENC_WITH_MITM: + return BT_GATT_PERM_WRITE_AUTHEN; + case SECURITY_MODE_LESC_ENC_WITH_MITM: + return BT_GATT_PERM_WRITE_LESC; + default: + return BT_GATT_PERM_WRITE; + } +} + static void service_ensure_capacity(bleio_service_obj_t *self, size_t needed) { if (self->attr_count + needed <= self->attr_capacity) { return; @@ -70,6 +146,7 @@ uint32_t _common_hal_bleio_service_construct(bleio_service_obj_t *self, self->start_handle = 0; self->end_handle = 0; self->registered = false; + self->next_retained = NULL; // Convert UUID to Zephyr format bleio_uuid_to_zephyr(uuid, &self->zephyr_uuid); @@ -106,6 +183,7 @@ void common_hal_bleio_service_deinit(bleio_service_obj_t *self) { bt_gatt_service_unregister(&self->zephyr_service); self->registered = false; } + service_release(self); if (self->attrs != NULL) { port_free(self->attrs); self->attrs = NULL; @@ -124,6 +202,7 @@ void common_hal_bleio_service_from_remote_service(bleio_service_obj_t *self, self->start_handle = 0; self->end_handle = 0; self->registered = false; + self->next_retained = NULL; self->attrs = NULL; self->attr_count = 0; self->attr_capacity = 0; @@ -229,7 +308,7 @@ void common_hal_bleio_service_add_characteristic(bleio_service_obj_t *self, BT_GATT_CCC_MANAGED_USER_DATA_INIT(bleio_ccc_changed_cb, bleio_ccc_write_cb, NULL); self->attrs[idx] = (struct bt_gatt_attr) { .uuid = (const struct bt_uuid *)&_uuid_ccc, - .perm = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + .perm = BT_GATT_PERM_READ | ccc_write_perm(characteristic->read_perm), .read = bt_gatt_attr_read_ccc, .write = bt_gatt_attr_write_ccc, .user_data = &characteristic->zephyr_ccc, @@ -262,4 +341,6 @@ void common_hal_bleio_service_add_characteristic(bleio_service_obj_t *self, raise_zephyr_error(err); } self->registered = true; + // Zephyr now points into this object; keep it alive until unregistered. + service_retain(self); } diff --git a/ports/zephyr-cp/common-hal/_bleio/Service.h b/ports/zephyr-cp/common-hal/_bleio/Service.h index 8c820fd204e..75731bc6e09 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Service.h +++ b/ports/zephyr-cp/common-hal/_bleio/Service.h @@ -29,4 +29,15 @@ typedef struct bleio_service_obj { size_t attr_capacity; struct bt_uuid_128 zephyr_uuid; bool registered; + // Link in the list of heap services retained while Zephyr's GATT database + // refers to them. See bleio_service_unregister_retained(). + struct bleio_service_obj *next_retained; } bleio_service_obj_t; + +// Unregister every user-created service still in Zephyr's GATT database and +// free their port-heap buffers. Call from bleio_user_reset(), while the GC heap +// the service objects live in is still valid. +void bleio_service_unregister_retained(void); + +// Mark the retained services as reachable during a GC pass. +void bleio_service_gc_collect(void); diff --git a/ports/zephyr-cp/common-hal/_bleio/__init__.c b/ports/zephyr-cp/common-hal/_bleio/__init__.c index e3c208a7d1d..b51db00835a 100644 --- a/ports/zephyr-cp/common-hal/_bleio/__init__.c +++ b/ports/zephyr-cp/common-hal/_bleio/__init__.c @@ -12,43 +12,70 @@ #include "common-hal/_bleio/__init__.h" #include "bindings/zephyr_kernel/__init__.h" #include "common-hal/_bleio/Connection.h" +#include "common-hal/_bleio/Service.h" #include "supervisor/shared/bluetooth/bluetooth.h" #include "supervisor/shared/tick.h" // The singleton _bleio.Adapter object bleio_adapter_obj_t common_hal_bleio_adapter_obj; +// Called once by the BLE workflow at boot and again on every `import _bleio`, +// so it must not disturb a running adapter: the workflow may be advertising or +// connected by the time user code imports the module. Starting the BLE stack happens in +// common_hal_bleio_adapter_set_enabled(), which the importer calls next. void common_hal_bleio_init(void) { common_hal_bleio_adapter_obj.base.type = &bleio_adapter_type; - bleio_adapter_reset(&common_hal_bleio_adapter_obj); bleio_connection_register_auth_callbacks(); } +// Tear down user BLE state at the end of a VM run, leaving the BLE workflow +// connection up. Runs while the VM heap is still valid. void bleio_user_reset(void) { if (common_hal_bleio_adapter_get_enabled(&common_hal_bleio_adapter_obj)) { // Stop any user scanning or advertising. common_hal_bleio_adapter_stop_scan(&common_hal_bleio_adapter_obj); common_hal_bleio_adapter_stop_advertising(&common_hal_bleio_adapter_obj); + + // Disconnect the connections that user code initiated or accepted with its + // own advertising. The BLE workflow connection is not user-owned and stays up. + // + // Clear each connection's pointer into the VM heap first: the heap is about + // to go away, and a disconnect completes asynchronously, possibly after the heap + // is gone. + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + bleio_connection_internal_t *connection = &bleio_connections[i]; + connection->connection_obj = mp_const_none; + if (connection->conn != NULL && connection->user_owned) { + common_hal_bleio_connection_disconnect(connection); + } + } + + // Remove references to any VM heap objects. + common_hal_bleio_adapter_obj.connection_objs = NULL; + common_hal_bleio_adapter_obj.scan_results = NULL; } + // Remove user-created services from Zephyr's GATT database and free their + // buffers. Zephyr indicates Service Changed to connected peers itself, and + // records it for bonded peers to receive when they next connect. + bleio_service_unregister_retained(); + // Maybe start advertising the BLE workflow. supervisor_bluetooth_background(); } +// Called after the VM heap is gone. On nordic and espressif this restarts the +// BLE stack when user code created GATT services, because their stacks can't +// remove services one at a time. Zephyr can, and bleio_user_reset() already did, +// so there is nothing left that requires dropping the workflow connection. void bleio_reset(void) { common_hal_bleio_adapter_obj.base.type = &bleio_adapter_type; - if (!common_hal_bleio_adapter_get_enabled(&common_hal_bleio_adapter_obj)) { - return; - } - - supervisor_stop_bluetooth(); - bleio_adapter_reset(&common_hal_bleio_adapter_obj); - common_hal_bleio_adapter_set_enabled(&common_hal_bleio_adapter_obj, false); - supervisor_start_bluetooth(); + bleio_clear_user_services_created(); } void common_hal_bleio_gc_collect(void) { bleio_adapter_gc_collect(&common_hal_bleio_adapter_obj); + bleio_service_gc_collect(); } // ======================================================================= diff --git a/ports/zephyr-cp/common-hal/analogio/AnalogIn.c b/ports/zephyr-cp/common-hal/analogio/AnalogIn.c new file mode 100644 index 00000000000..70f27962495 --- /dev/null +++ b/ports/zephyr-cp/common-hal/analogio/AnalogIn.c @@ -0,0 +1,173 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/analogio/AnalogIn.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include + +#include +#include + +#include "bindings/zephyr_kernel/__init__.h" +#include "py/runtime.h" + +// Zephyr has no API to query an ADC's native resolution: adc_sequence.resolution +// is set by the caller and drivers reject values they can't meet (from probe +// time, before any conversion starts). Ask the driver for the highest +// resolution it accepts with a descending ladder of one-read probes. The +// emulated ADC answers 16; the SAADC only accepts 8/10/12/14 and the Renesas +// RA ADC its fixed hardware resolution. +static const uint8_t adc_resolutions[] = {16, 14, 12, 10, 8}; + +// Every (reference, gain) pair over the VDD-derived references with gain +// cancelling the divider, i.e. full scale = reference / gain = VDD, which is +// what the 16-bit analogio scaling contract assumes. Drivers only accept the +// combinations their hardware supports (the nRF52 family SAADC has no full-VDD +// reference and lands on VDD/4); probe down the ladder at construct time. +static const struct { + enum adc_reference reference; + enum adc_gain gain; +} adc_railings[] = { + {ADC_REF_VDD_1, ADC_GAIN_1}, + {ADC_REF_VDD_1_2, ADC_GAIN_1_2}, + {ADC_REF_VDD_1_3, ADC_GAIN_1_3}, + {ADC_REF_VDD_1_4, ADC_GAIN_1_4}, +}; + +void common_hal_analogio_analogin_construct(analogio_analogin_obj_t *self, const mcu_pin_obj_t *pin) { + // Allocate the pad's analog input through the iobroker module: the call + // claims the pad so that bus and GPIO allocations refuse it, resolves the + // fixed analog input the pad is bonded to (identity on the emulated ADC) + // and hands out a free channel slot on the ADC device. + const struct device *adc; + uint8_t channel; + uint8_t input; + int res = iobroker_adc_allocate(pin->package_pin, &adc, &channel, &input); + if (res == -ENOSYS) { + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_AnalogIn); + } + if (res == -EINVAL) { + // The pad exists but isn't bonded to an analog input. Match the other + // ports, which raise ValueError("Invalid pin") for such pins. + raise_ValueError_invalid_pin(); + } + if (res < 0) { + raise_zephyr_error(res); + } + + if (!device_is_ready(adc)) { + iobroker_adc_release(adc, channel); + raise_zephyr_error(-ENODEV); + } + + // Sample against the analog supply, like the other ports (SAMD's + // INTVCC1, RP2040's VREF pin, STM32/Espressif's supply-referenced + // ranges): full scale is VDD/VDDA, so a full-rail input reads 65535. + struct adc_channel_cfg channel_cfg = { + .acquisition_time = ADC_ACQ_TIME_DEFAULT, + .channel_id = channel, + }; + #ifdef CONFIG_ADC_CONFIGURABLE_INPUTS + channel_cfg.input_positive = input; + #endif + + int err = 0; + for (uint8_t i = 0; i < ARRAY_SIZE(adc_railings); i++) { + channel_cfg.reference = adc_railings[i].reference; + channel_cfg.gain = adc_railings[i].gain; + err = adc_channel_setup(adc, &channel_cfg); + if (err != -EINVAL) { + break; + } + } + if (err != 0) { + iobroker_adc_release(adc, channel); + raise_zephyr_error(err); + } + + // Do dummy reads to determine what resolution the device actually support. + uint16_t probe = 0; + uint8_t resolution = 0; + for (uint8_t i = 0; i < ARRAY_SIZE(adc_resolutions); i++) { + struct adc_sequence probe_sequence = { + .channels = BIT(channel), + .buffer = &probe, + .buffer_size = sizeof(probe), + .resolution = adc_resolutions[i], + }; + err = adc_read(adc, &probe_sequence); + if (err == 0) { + resolution = adc_resolutions[i]; + break; + } + if (err != -EINVAL && err != -ENOTSUP) { + // Not a resolution rejection (drivers report those before sampling): + // surface driver errors like -EBUSY rather than skipping them. + iobroker_adc_release(adc, channel); + raise_zephyr_error(err); + } + } + if (resolution == 0) { + iobroker_adc_release(adc, channel); + raise_zephyr_error(-ENOTSUP); + } + + self->pin = pin; + self->adc = adc; + self->channel_id = channel; + self->resolution = resolution; +} + +bool common_hal_analogio_analogin_deinited(analogio_analogin_obj_t *self) { + return self->adc == NULL; +} + +void common_hal_analogio_analogin_deinit(analogio_analogin_obj_t *self) { + if (common_hal_analogio_analogin_deinited(self)) { + return; + } + + (void)iobroker_adc_release(self->adc, self->channel_id); + self->pin = NULL; + self->adc = NULL; +} + +uint16_t common_hal_analogio_analogin_get_value(analogio_analogin_obj_t *self) { + // Sample at the ADC's full hardware resolution (probed at construct) and + // stretch the raw code to CircuitPython's 16-bit contract relative to the + // reference voltage, which is what the analogio contract is. + uint16_t raw = 0; + struct adc_sequence sequence = { + .channels = BIT(self->channel_id), + .buffer = &raw, + .buffer_size = sizeof(raw), + .resolution = self->resolution, + }; + + int err = adc_read(self->adc, &sequence); + if (err != 0) { + raise_zephyr_error(err); + } + + if (self->resolution >= 16) { + return raw; + } + + // Mirror the top 16 - resolution bits into the bottom: for a 14-bit read, + // (value << 2) | (value >> 12) (atmel-samd does the same for its 12-bit + // reads). raw << shift fits in 16 bits because raw is at most resolution + // bits wide. Zero and full scale map to themselves, unlike scaling by + // 65535 / max_raw which bunches codes near zero. + const uint8_t shift = 16 - self->resolution; + return (uint16_t)((raw << shift) | (raw >> (self->resolution - shift))); +} + +float common_hal_analogio_analogin_get_reference_voltage(analogio_analogin_obj_t *self) { + // Hard-coded until the reference-voltage query API (upstream adc_ref_get()) + // is available; every other port also reports the nominal analog supply. + return 3.3f; +} diff --git a/ports/broadcom/common-hal/digitalio/DigitalInOut.h b/ports/zephyr-cp/common-hal/analogio/AnalogIn.h similarity index 54% rename from ports/broadcom/common-hal/digitalio/DigitalInOut.h rename to ports/zephyr-cp/common-hal/analogio/AnalogIn.h index 69520a92bff..e8fababe1e0 100644 --- a/ports/broadcom/common-hal/digitalio/DigitalInOut.h +++ b/ports/zephyr-cp/common-hal/analogio/AnalogIn.h @@ -1,17 +1,21 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2020 Scott Shawcroft for Adafruit Industries +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries LLC // // SPDX-License-Identifier: MIT #pragma once #include "common-hal/microcontroller/Pin.h" + +#include + #include "py/obj.h" typedef struct { mp_obj_base_t base; const mcu_pin_obj_t *pin; - bool output; - bool open_drain; -} digitalio_digitalinout_obj_t; + const struct device *adc; + uint8_t channel_id; + uint8_t resolution; +} analogio_analogin_obj_t; diff --git a/ports/zephyr-cp/common-hal/analogio/AnalogOut.c b/ports/zephyr-cp/common-hal/analogio/AnalogOut.c new file mode 100644 index 00000000000..a558a0e2b82 --- /dev/null +++ b/ports/zephyr-cp/common-hal/analogio/AnalogOut.c @@ -0,0 +1,106 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/analogio/AnalogOut.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include +#include +#include + +#include "bindings/zephyr_kernel/__init__.h" +#include "py/runtime.h" + +void common_hal_analogio_analogout_construct(analogio_analogout_obj_t *self, const mcu_pin_obj_t *pin) { + // Allocate the pad's analog output through the iobroker module: the call + // claims the pad so that bus and GPIO allocations refuse it and hands out + // a free channel slot on the DAC device. + const struct device *dac; + uint8_t channel; + uint8_t input; + int res = iobroker_dac_allocate(pin->package_pin, &dac, &channel, &input); + if (res == -ENOSYS) { + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_AnalogOut); + } + if (res == -EINVAL) { + // The pad exists but isn't bonded to a DAC output. Match the other + // ports, which raise ValueError("Invalid pin") for such pins. + raise_ValueError_invalid_pin(); + } + if (res < 0) { + raise_zephyr_error(res); + } + + struct dac_channel_cfg channel_cfg = { + .channel_id = channel, + .buffered = true, + .internal = false, + }; + + // Ask the driver for the highest resolution it accepts, instead of picking + // one per driver Kconfig: drivers reject an unsupported resolution from + // dac_channel_setup() with -EINVAL/-ENOTSUP before touching hardware. The + // emulated test DAC answers 16; the RA DAC12 and NXP LPDAC only accept + // their 12-bit hardware resolution; writes are scaled down in + // common_hal_analogio_analogout_set_value(). + static const uint8_t dac_resolutions[] = {16, 12}; + for (uint8_t i = 0; i < ARRAY_SIZE(dac_resolutions); i++) { + channel_cfg.resolution = dac_resolutions[i]; + res = dac_channel_setup(dac, &channel_cfg); + if (res == 0 || (res != -EINVAL && res != -ENOTSUP)) { + // Not a resolution rejection: surface driver errors like -EBUSY. + break; + } + } + if (res != 0) { + iobroker_dac_release(dac, channel); + raise_zephyr_error(res); + } + + self->pin = pin; + self->dac = dac; + self->channel_id = channel; + self->resolution = channel_cfg.resolution; +} + +bool common_hal_analogio_analogout_deinited(analogio_analogout_obj_t *self) { + return self->dac == NULL; +} + +void common_hal_analogio_analogout_deinit(analogio_analogout_obj_t *self) { + if (common_hal_analogio_analogout_deinited(self)) { + return; + } + + (void)iobroker_dac_release(self->dac, self->channel_id); + self->pin = NULL; + self->dac = NULL; +} + +void common_hal_analogio_analogout_set_value(analogio_analogout_obj_t *self, uint16_t value) { + if (common_hal_analogio_analogout_deinited(self)) { + return; + } + + if (self->resolution != 16) { + // CircuitPython's 16-bit code scales down to the DAC's resolution + // (probed at construct) with converter rounding: (value * out_max + + // in_max / 2) / in_max, the same rounding the emulated DAC applies in + // reverse. + const uint32_t in_max = 65535u; + const uint32_t out_max = (1u << self->resolution) - 1u; + uint32_t scaled = ((uint32_t)value * out_max + in_max / 2u) / in_max; + if (scaled > out_max) { + scaled = out_max; + } + value = (uint16_t)scaled; + } + + int err = dac_write_value(self->dac, self->channel_id, value); + if (err != 0) { + raise_zephyr_error(err); + } +} diff --git a/ports/litex/common-hal/digitalio/DigitalInOut.h b/ports/zephyr-cp/common-hal/analogio/AnalogOut.h similarity index 50% rename from ports/litex/common-hal/digitalio/DigitalInOut.h rename to ports/zephyr-cp/common-hal/analogio/AnalogOut.h index f58b23832b1..333163cb2b1 100644 --- a/ports/litex/common-hal/digitalio/DigitalInOut.h +++ b/ports/zephyr-cp/common-hal/analogio/AnalogOut.h @@ -1,15 +1,21 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2017 Scott Shawcroft for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2019 Lucian Copeland for Adafruit Industries +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries LLC // // SPDX-License-Identifier: MIT #pragma once +#include "py/obj.h" + +#include + #include "common-hal/microcontroller/Pin.h" typedef struct { mp_obj_base_t base; const mcu_pin_obj_t *pin; -} digitalio_digitalinout_obj_t; + const struct device *dac; + uint8_t channel_id; + uint8_t resolution; +} analogio_analogout_obj_t; diff --git a/ports/zephyr-cp/common-hal/analogio/__init__.c b/ports/zephyr-cp/common-hal/analogio/__init__.c new file mode 100644 index 00000000000..803ce3b3cb7 --- /dev/null +++ b/ports/zephyr-cp/common-hal/analogio/__init__.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +// No analogio module functions. diff --git a/ports/zephyr-cp/common-hal/busio/I2C.c b/ports/zephyr-cp/common-hal/busio/I2C.c index 84e95721b27..3ff66b1de13 100644 --- a/ports/zephyr-cp/common-hal/busio/I2C.c +++ b/ports/zephyr-cp/common-hal/busio/I2C.c @@ -5,9 +5,14 @@ // SPDX-License-Identifier: MIT #include "shared-bindings/busio/I2C.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "bindings/zephyr_kernel/__init__.h" #include "py/mperrno.h" #include "py/runtime.h" +#include + #include #include #include @@ -18,30 +23,87 @@ mp_obj_t common_hal_busio_i2c_construct_from_device(busio_i2c_obj_t *self, const self->i2c_device = i2c_device; k_mutex_init(&self->mutex); self->has_lock = false; + self->dynamic = false; + self->sda = NULL; + self->scl = NULL; return MP_OBJ_FROM_PTR(self); } -// Standard busio construct - not used in Zephyr port (devices come from device tree) +static void raise_no_i2c_peripheral(int err) { + if (err == -ENODEV) { + mp_raise_ValueError(MP_ERROR_TEXT("All I2C peripherals are in use")); + } + if (err == -EBUSY) { + mp_raise_ValueError(MP_ERROR_TEXT("Internal resource(s) in use")); + } + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_I2C); +} + +// Standard busio construct: pick a free peripheral instance and route it to +// the requested pins at runtime (supported on nRF SoCs). void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, const mcu_pin_obj_t *scl, const mcu_pin_obj_t *sda, uint32_t frequency, uint32_t timeout_ms) { - mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_I2C); + const struct device *dev = NULL; + int ret = iobroker_i2c_allocate(sda->package_pin, scl->package_pin, &dev); + if (ret < 0) { + raise_no_i2c_peripheral(ret); + } + + common_hal_busio_i2c_construct_from_device(self, dev); + self->dynamic = true; + self->sda = sda; + self->scl = scl; + + // Initialize the deferred device now that it is routed to the requested + // pins. Fixed devicetree instances are already initialized (-EALREADY). + int init_ret = device_init(dev); + if (init_ret < 0 && init_ret != -EALREADY) { + // The failed init may have routed pins and left the device in a + // partial state; deinit gives up the claim and resets the pins. + common_hal_busio_i2c_deinit(self); + raise_zephyr_error(init_ret); + } + + // Apply the requested bus frequency. Zephyr nRF drivers support 100k, + // 400k and (on TWIM) 1M. + uint8_t speed; + if (frequency <= 100000) { + speed = I2C_SPEED_STANDARD; + } else if (frequency <= 400000) { + speed = I2C_SPEED_FAST; + } else { + speed = I2C_SPEED_FAST_PLUS; + } + int config_ret = i2c_configure(self->i2c_device, I2C_SPEED_SET(speed)); + if (config_ret < 0) { + common_hal_busio_i2c_deinit(self); + raise_zephyr_error(config_ret); + } } bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { - // Always leave it active (managed by Zephyr) - return false; + return self->i2c_device == NULL; } void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) { if (common_hal_busio_i2c_deinited(self)) { return; } - // Always leave it active (managed by Zephyr) + if (self->dynamic) { + // The release de-inits the device, which applies its low-power + // pinctrl state and leaves the routed pins disconnected. + (void)iobroker_release(self->i2c_device); + self->sda = NULL; + self->scl = NULL; + self->i2c_device = NULL; + } } void common_hal_busio_i2c_mark_deinit(busio_i2c_obj_t *self) { - // Not needed for Zephyr port + if (self->dynamic) { + self->i2c_device = NULL; + } } bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) { @@ -146,7 +208,3 @@ mp_negative_errno_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint1 return 0; } - -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - // Not needed for Zephyr port (devices are managed by Zephyr) -} diff --git a/ports/zephyr-cp/common-hal/busio/I2C.h b/ports/zephyr-cp/common-hal/busio/I2C.h index 4fa877739b7..607a0422d9c 100644 --- a/ports/zephyr-cp/common-hal/busio/I2C.h +++ b/ports/zephyr-cp/common-hal/busio/I2C.h @@ -7,13 +7,21 @@ #pragma once #include "py/obj.h" + +#include "common-hal/microcontroller/Pin.h" #include typedef struct { mp_obj_base_t base; const struct device *i2c_device; + const mcu_pin_obj_t *sda; + const mcu_pin_obj_t *scl; struct k_mutex mutex; bool has_lock; + // True when the underlying Zephyr device was dynamically routed to the + // pins below at construction time. Such objects deinitialize the device + // and release their pins. + bool dynamic; } busio_i2c_obj_t; // Helper function to construct from Zephyr device tree device diff --git a/ports/zephyr-cp/common-hal/busio/SPI.c b/ports/zephyr-cp/common-hal/busio/SPI.c index 2864c90b490..122bc478578 100644 --- a/ports/zephyr-cp/common-hal/busio/SPI.c +++ b/ports/zephyr-cp/common-hal/busio/SPI.c @@ -5,12 +5,18 @@ // SPDX-License-Identifier: MIT #include "shared-bindings/busio/SPI.h" +#include "shared-bindings/microcontroller/Pin.h" + #include "py/mperrno.h" #include "py/runtime.h" #include "py/gc.h" #include "shared/runtime/interrupt_char.h" #include "supervisor/port.h" +#include "bindings/zephyr_kernel/__init__.h" + +#include +#include #include #include #include @@ -22,6 +28,10 @@ mp_obj_t common_hal_busio_spi_construct_from_device(busio_spi_obj_t *self, const k_mutex_init(&self->mutex); self->has_lock = false; self->active_config = 0; + self->dynamic = false; + self->clock = NULL; + self->mosi = NULL; + self->miso = NULL; k_poll_signal_init(&self->signal); @@ -34,27 +44,69 @@ mp_obj_t common_hal_busio_spi_construct_from_device(busio_spi_obj_t *self, const return MP_OBJ_FROM_PTR(self); } -// Standard busio construct - not used in Zephyr port (devices come from device tree) +// Standard busio construct: pick a free peripheral instance and route it to +// the requested pins at runtime (supported on nRF SoCs). void common_hal_busio_spi_construct(busio_spi_obj_t *self, const mcu_pin_obj_t *clock, const mcu_pin_obj_t *mosi, const mcu_pin_obj_t *miso, bool half_duplex) { - mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_SPI); + if (half_duplex) { + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_half_duplex); + } + + const struct device *dev = NULL; + int ret = iobroker_spi_allocate(clock->package_pin, + mosi != NULL ? mosi->package_pin : IOBROKER_NO_PIN, + miso != NULL ? miso->package_pin : IOBROKER_NO_PIN, &dev); + if (ret < 0) { + if (ret == -ENODEV) { + mp_raise_ValueError(MP_ERROR_TEXT("All SPI peripherals are in use")); + } + if (ret == -EBUSY) { + mp_raise_ValueError(MP_ERROR_TEXT("Internal resource(s) in use")); + } + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_SPI); + } + + common_hal_busio_spi_construct_from_device(self, dev); + self->dynamic = true; + self->clock = clock; + self->mosi = mosi; + self->miso = miso; + + // Initialize the deferred device now that it is routed to the requested + // pins. Fixed devicetree instances are already initialized (-EALREADY). + int init_ret = device_init(dev); + if (init_ret < 0 && init_ret != -EALREADY) { + // The failed init may have routed pins and left the device in a + // partial state; deinit gives up the claim and resets the pins. + common_hal_busio_spi_deinit(self); + raise_zephyr_error(init_ret); + } } bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { - // Always leave it active - return false; + return self->spi_device == NULL; } void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { if (common_hal_busio_spi_deinited(self)) { return; } - // Always leave it active + if (self->dynamic) { + // The release de-inits the device, which applies its low-power + // pinctrl state and leaves the routed pins disconnected. + (void)iobroker_release(self->spi_device); + self->clock = NULL; + self->mosi = NULL; + self->miso = NULL; + self->spi_device = NULL; + } } void common_hal_busio_spi_mark_deinit(busio_spi_obj_t *self) { - // Not needed for Zephyr port + if (self->dynamic) { + self->spi_device = NULL; + } } bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) { @@ -275,7 +327,3 @@ uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self) { uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) { return (self->config[self->active_config].operation & SPI_MODE_CPOL) ? 1 : 0; } - -void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { - // Not needed for Zephyr port (devices are managed by Zephyr) -} diff --git a/ports/zephyr-cp/common-hal/busio/SPI.h b/ports/zephyr-cp/common-hal/busio/SPI.h index 87411c9825c..fa5de574116 100644 --- a/ports/zephyr-cp/common-hal/busio/SPI.h +++ b/ports/zephyr-cp/common-hal/busio/SPI.h @@ -7,6 +7,8 @@ #pragma once #include "py/obj.h" + +#include "common-hal/microcontroller/Pin.h" #include #include @@ -14,10 +16,16 @@ typedef struct { mp_obj_base_t base; const struct device *spi_device; struct k_mutex mutex; - bool has_lock; struct spi_config config[2]; // Two configs for pointer comparison by driver uint8_t active_config; // Index of currently active config (0 or 1) struct k_poll_signal signal; + const mcu_pin_obj_t *clock; + const mcu_pin_obj_t *mosi; + const mcu_pin_obj_t *miso; + // True when the underlying Zephyr device was dynamically routed to the + // pins above at construction time. + bool dynamic; + bool has_lock; } busio_spi_obj_t; // Helper function for Zephyr-specific initialization from device tree diff --git a/ports/zephyr-cp/common-hal/busio/UART.c b/ports/zephyr-cp/common-hal/busio/UART.c index af1de0e9023..17fc2ddecd4 100644 --- a/ports/zephyr-cp/common-hal/busio/UART.c +++ b/ports/zephyr-cp/common-hal/busio/UART.c @@ -6,17 +6,23 @@ #include "shared-bindings/microcontroller/__init__.h" #include "shared-bindings/busio/UART.h" +#include "shared-bindings/microcontroller/Pin.h" #include "shared/runtime/interrupt_char.h" #include "py/mpconfig.h" #include "py/gc.h" +#include "py/mphal.h" #include "py/mperrno.h" #include "py/runtime.h" #include "py/stream.h" +#include "bindings/zephyr_kernel/__init__.h" +#include "supervisor/port.h" + #include #include +#include #include #include LOG_MODULE_REGISTER(busio_uart); @@ -42,21 +48,27 @@ static void serial_cb(const struct device *dev, void *user_data) { common_hal_busio_uart_clear_rx_buffer(self); mp_sched_keyboard_interrupt(); } else if (!self->rx_paused) { - if (k_msgq_put(&self->msgq, &c, K_NO_WAIT) != 0) { + if (k_msgq_put(&self->msgq, &c, K_NO_WAIT) == 0) { + // Wake the main task so it can service the new RX data + // instead of sleeping out its full timeout. + port_wake_main_task_from_isr(); + } else { self->rx_paused = true; } } } } -void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { - // Not needed for Zephyr port (devices are managed by Zephyr) -} - // Helper function for Zephyr-specific initialization from device tree mp_obj_t common_hal_busio_uart_construct_from_device(busio_uart_obj_t *self, const struct device *uart_device, uint16_t receiver_buffer_size, byte *receiver_buffer) { self->base.type = &busio_uart_type; self->uart_device = uart_device; + self->dynamic = false; + self->receiver_buffer = NULL; + self->tx = NULL; + self->rx = NULL; + self->rts = NULL; + self->cts = NULL; int ret = uart_irq_callback_user_data_set(uart_device, serial_cb, self); if (ret < 0) { @@ -73,7 +85,8 @@ mp_obj_t common_hal_busio_uart_construct_from_device(busio_uart_obj_t *self, con return MP_OBJ_FROM_PTR(self); } -// Standard busio construct - not used in Zephyr port (devices come from device tree) +// Standard busio construct: pick a free peripheral instance and route it to +// the requested pins at runtime (supported on nRF SoCs). void common_hal_busio_uart_construct(busio_uart_obj_t *self, const mcu_pin_obj_t *tx, const mcu_pin_obj_t *rx, const mcu_pin_obj_t *rts, const mcu_pin_obj_t *cts, @@ -81,15 +94,102 @@ void common_hal_busio_uart_construct(busio_uart_obj_t *self, uint32_t baudrate, uint8_t bits, busio_uart_parity_t parity, uint8_t stop, mp_float_t timeout, uint16_t receiver_buffer_size, byte *receiver_buffer, bool sigint_enabled) { - mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_UART); + if (rs485_dir != NULL) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("RS485")); + } + // nRF UARTE only supports 8 data bits. + mp_arg_validate_int(bits, 8, MP_QSTR_bits); + + const struct device *dev = NULL; + int ret = iobroker_uart_allocate(tx != NULL ? tx->package_pin : IOBROKER_NO_PIN, + rx != NULL ? rx->package_pin : IOBROKER_NO_PIN, + rts != NULL ? rts->package_pin : IOBROKER_NO_PIN, + cts != NULL ? cts->package_pin : IOBROKER_NO_PIN, &dev); + if (ret < 0) { + if (ret == -ENODEV) { + mp_raise_ValueError(MP_ERROR_TEXT("All UART peripherals are in use")); + } + if (ret == -EBUSY) { + mp_raise_ValueError(MP_ERROR_TEXT("Internal resource(s) in use")); + } + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_UART); + } + + bool allocated_buffer = false; + if (receiver_buffer == NULL) { + receiver_buffer = m_malloc(receiver_buffer_size); + allocated_buffer = true; + } + + common_hal_busio_uart_construct_from_device(self, dev, receiver_buffer_size, receiver_buffer); + self->dynamic = true; + self->receiver_buffer = allocated_buffer ? receiver_buffer : NULL; + self->tx = tx; + self->rx = rx; + self->rts = rts; + self->cts = cts; + + // Initialize the deferred device now that it is routed to the requested + // pins. Fixed devicetree instances are already initialized (-EALREADY). + int init_ret = device_init(dev); + if (init_ret < 0 && init_ret != -EALREADY) { + // The failed init may have routed pins and left the device in a + // partial state; deinit gives up the claim and resets the pins. + common_hal_busio_uart_deinit(self); + raise_zephyr_error(init_ret); + } + + // Apply line configuration. + struct uart_config config = { + .baudrate = baudrate, + .data_bits = UART_CFG_DATA_BITS_8, + .parity = (parity == BUSIO_UART_PARITY_NONE) ? UART_CFG_PARITY_NONE : + ((parity == BUSIO_UART_PARITY_EVEN) ? UART_CFG_PARITY_EVEN : UART_CFG_PARITY_ODD), + .stop_bits = (stop == 1) ? UART_CFG_STOP_BITS_1 : UART_CFG_STOP_BITS_2, + .flow_ctrl = (rts != NULL && cts != NULL) ? UART_CFG_FLOW_CTRL_RTS_CTS : UART_CFG_FLOW_CTRL_NONE, + }; + int config_ret = uart_configure(self->uart_device, &config); + if (config_ret < 0) { + LOG_ERR("uart_configure failed: %d (baudrate=%u stop=%u flow=%u)", + config_ret, baudrate, stop, (rts != NULL && cts != NULL)); + common_hal_busio_uart_deinit(self); + raise_zephyr_error(config_ret); + } + + self->timeout = K_USEC((uint64_t)(timeout * 1000000)); } bool common_hal_busio_uart_deinited(busio_uart_obj_t *self) { - return !device_is_ready(self->uart_device); + return self->uart_device == NULL; } void common_hal_busio_uart_deinit(busio_uart_obj_t *self) { - // Leave it active (managed by Zephyr) + if (common_hal_busio_uart_deinited(self)) { + return; + } + if (self->dynamic) { + // The device may not be fully initialized: construct de-inits this + // object when device_init() fails partway through. Zephyr then + // reports it not ready (init_res != 0), so only poke the driver + // when it is really up and running. The iobroker claim and any + // routed pins are still given up below. + if (device_is_ready(self->uart_device)) { + uart_irq_rx_disable(self->uart_device); + uart_irq_callback_user_data_set(self->uart_device, NULL, NULL); + } + // The release de-inits the device, which applies its low-power + // pinctrl state and leaves the routed pins disconnected. + (void)iobroker_release(self->uart_device); + self->tx = NULL; + self->rx = NULL; + self->rts = NULL; + self->cts = NULL; + if (self->receiver_buffer != NULL) { + m_free(self->receiver_buffer); + self->receiver_buffer = NULL; + } + self->uart_device = NULL; + } } // Read characters. diff --git a/ports/zephyr-cp/common-hal/busio/UART.h b/ports/zephyr-cp/common-hal/busio/UART.h index be0b7ff83a9..3e8614a8dd2 100644 --- a/ports/zephyr-cp/common-hal/busio/UART.h +++ b/ports/zephyr-cp/common-hal/busio/UART.h @@ -8,6 +8,8 @@ #include "py/obj.h" +#include "common-hal/microcontroller/Pin.h" + #include typedef struct { @@ -18,8 +20,18 @@ typedef struct { k_timeout_t timeout; k_timeout_t write_timeout; + byte *receiver_buffer; + const mcu_pin_obj_t *tx; + const mcu_pin_obj_t *rx; + const mcu_pin_obj_t *rts; + const mcu_pin_obj_t *cts; bool rx_paused; // set by irq if no space in rbuf + + // True when the underlying Zephyr device was dynamically routed to the + // pins above at construction time. Such objects own their receiver + // buffer and deinitialize the device and release their pins. + bool dynamic; } busio_uart_obj_t; // Helper function for Zephyr-specific initialization from device tree diff --git a/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.c b/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.c index 0da16a9b720..7dffd6f7af7 100644 --- a/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.c +++ b/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.c @@ -6,24 +6,32 @@ #include "shared-bindings/digitalio/DigitalInOut.h" +#include + #include #include -void common_hal_digitalio_digitalinout_never_reset( - digitalio_digitalinout_obj_t *self) { -} - digitalinout_result_t common_hal_digitalio_digitalinout_construct( digitalio_digitalinout_obj_t *self, const mcu_pin_obj_t *pin) { - claim_pin(pin); + // Claim the pin in the iobroker module so that bus allocations refuse + // it while this object holds it. The call also resolves the GPIO + // controller device and pin number from the pin's global number; they are + // kept in the object for every later pad operation. + int ret = iobroker_gpio_allocate(pin->package_pin, &self->port, &self->number); + if (ret < 0) { + return DIGITALINOUT_PIN_BUSY; + } + self->pin = pin; - if (!device_is_ready(pin->port)) { + if (!device_is_ready(self->port)) { printk("Port device not ready\n"); + common_hal_digitalio_digitalinout_deinit(self); return DIGITALINOUT_PIN_BUSY; } - if (gpio_pin_configure(pin->port, pin->number, GPIO_INPUT) != 0) { + if (gpio_pin_configure(self->port, self->number, GPIO_INPUT) != 0) { + common_hal_digitalio_digitalinout_deinit(self); return DIGITALINOUT_PIN_BUSY; } self->direction = DIRECTION_INPUT; @@ -40,7 +48,7 @@ void common_hal_digitalio_digitalinout_deinit(digitalio_digitalinout_obj_t *self return; } - reset_pin(self->pin); + (void)iobroker_gpio_release(self->port, self->number); self->pin = NULL; } @@ -68,7 +76,7 @@ digitalio_direction_t common_hal_digitalio_digitalinout_get_direction( void common_hal_digitalio_digitalinout_set_value( digitalio_digitalinout_obj_t *self, bool value) { - int res = gpio_pin_set(self->pin->port, self->pin->number, value); + int res = gpio_pin_set(self->port, self->number, value); if (res != 0) { printk("Failed to set value %d\n", res); } @@ -81,7 +89,7 @@ bool common_hal_digitalio_digitalinout_get_value( if (self->direction == DIRECTION_OUTPUT) { return self->value; } - return gpio_pin_get(self->pin->port, self->pin->number) == 1; + return gpio_pin_get(self->port, self->number) == 1; } digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode( @@ -92,7 +100,7 @@ digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode( if (drive_mode == DRIVE_MODE_OPEN_DRAIN) { flags |= GPIO_OPEN_DRAIN; } - int res = gpio_pin_configure(self->pin->port, self->pin->number, flags); + int res = gpio_pin_configure(self->port, self->number, flags); if (res != 0) { // TODO: Fake open drain. printk("Failed to set drive mode %d\n", res); @@ -115,7 +123,7 @@ digitalinout_result_t common_hal_digitalio_digitalinout_set_pull( } else if (pull == PULL_DOWN) { pull_flags = GPIO_PULL_DOWN; } - if (gpio_pin_configure(self->pin->port, self->pin->number, GPIO_INPUT | pull_flags) != 0) { + if (gpio_pin_configure(self->port, self->number, GPIO_INPUT | pull_flags) != 0) { return DIGITALINOUT_INVALID_PULL; } self->pull = pull; diff --git a/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.h b/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.h index 9e0c3265268..4d21bb46a10 100644 --- a/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.h +++ b/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.h @@ -14,6 +14,10 @@ typedef struct { mp_obj_base_t base; const mcu_pin_obj_t *pin; + // GPIO controller device and pin number within it, resolved from the + // pin's global number by the gpio allocate call at construct time. + const struct device *port; + gpio_pin_t number; digitalio_direction_t direction; bool value; digitalio_drive_mode_t drive_mode; diff --git a/ports/zephyr-cp/common-hal/microcontroller/Pin.c b/ports/zephyr-cp/common-hal/microcontroller/Pin.c index 66882b6b5f0..d37214a1707 100644 --- a/ports/zephyr-cp/common-hal/microcontroller/Pin.c +++ b/ports/zephyr-cp/common-hal/microcontroller/Pin.c @@ -5,69 +5,14 @@ // SPDX-License-Identifier: MIT #include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/digitalio/DigitalInOut.h" #include "py/mphal.h" -// Bit mask of claimed pins on each of up to two ports. nrf52832 has one port; nrf52840 has two. -// static uint32_t claimed_pins[GPIO_COUNT]; -// static uint32_t never_reset_pins[GPIO_COUNT]; +#include -void reset_all_pins(void) { - // for (size_t i = 0; i < GPIO_COUNT; i++) { - // claimed_pins[i] = never_reset_pins[i]; - // } - - // for (uint32_t pin = 0; pin < NUMBER_OF_PINS; ++pin) { - // if ((never_reset_pins[nrf_pin_port(pin)] & (1 << nrf_relative_pin_number(pin))) != 0) { - // continue; - // } - // nrf_gpio_cfg_default(pin); - // } - - // // After configuring SWD because it may be shared. - // reset_speaker_enable_pin(); -} - -// Mark pin as free and return it to a quiescent state. -void reset_pin(const mcu_pin_obj_t *pin) { - - // Clear claimed bit. - // claimed_pins[nrf_pin_port(pin_number)] &= ~(1 << nrf_relative_pin_number(pin_number)); - // never_reset_pins[nrf_pin_port(pin_number)] &= ~(1 << nrf_relative_pin_number(pin_number)); -} - - -void never_reset_pin_number(uint8_t pin_number) { - // never_reset_pins[nrf_pin_port(pin_number)] |= 1 << nrf_relative_pin_number(pin_number); -} - -void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - never_reset_pin_number(pin->number); -} - -void common_hal_reset_pin(const mcu_pin_obj_t *pin) { +bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { if (pin == NULL) { - return; + return true; } - reset_pin(pin); -} - -void claim_pin(const mcu_pin_obj_t *pin) { - // Set bit in claimed_pins bitmask. - // claimed_pins[nrf_pin_port(pin->number)] |= 1 << nrf_relative_pin_number(pin->number); -} - - -bool pin_number_is_free(uint8_t pin_number) { - return false; // !(claimed_pins[nrf_pin_port(pin_number)] & (1 << nrf_relative_pin_number(pin_number))); -} - -bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { - return true; - -} - -void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { - claim_pin(pin); + return !iobroker_pin_in_use(pin->package_pin); } diff --git a/ports/zephyr-cp/common-hal/microcontroller/Pin.h b/ports/zephyr-cp/common-hal/microcontroller/Pin.h index d38ab9bd200..c3ec3869d1c 100644 --- a/ports/zephyr-cp/common-hal/microcontroller/Pin.h +++ b/ports/zephyr-cp/common-hal/microcontroller/Pin.h @@ -9,16 +9,15 @@ #include "py/mphal.h" #include "py/obj.h" +#include #include typedef struct { mp_obj_base_t base; - const struct device *port; - gpio_pin_t number; + // Package pin of the SoC package the pad is bonded to, resolved at + // build time from the board's package pin map. IOBROKER_NO_PIN when the + // pad has no entry in the map (or the SoC has no package pin map). + package_pin_t package_pin; } mcu_pin_obj_t; #include "autogen-pins.h" - -void reset_all_pins(void); -void reset_pin(const mcu_pin_obj_t *pin); -void claim_pin(const mcu_pin_obj_t *pin); diff --git a/ports/zephyr-cp/common-hal/microcontroller/__init__.c b/ports/zephyr-cp/common-hal/microcontroller/__init__.c index be33cd26c9a..195dd79779c 100644 --- a/ports/zephyr-cp/common-hal/microcontroller/__init__.c +++ b/ports/zephyr-cp/common-hal/microcontroller/__init__.c @@ -17,6 +17,7 @@ #include "shared-bindings/microcontroller/Processor.h" #include "supervisor/filesystem.h" +#include "adaboot/update_mode.h" #include "supervisor/port.h" #include "supervisor/shared/safe_mode.h" @@ -65,16 +66,15 @@ void common_hal_mcu_enable_interrupts() { } void common_hal_mcu_on_next_reset(mcu_runmode_t runmode) { - enum { DFU_MAGIC_UF2_RESET = 0x57 }; - uint8_t new_value = 0; if (runmode == RUNMODE_BOOTLOADER || runmode == RUNMODE_UF2) { - new_value = DFU_MAGIC_UF2_RESET; + // Both modes ask for the bootloader's update mode, the state a + // double-tap of the reset button produces. + adaboot_request_update_mode(true); + } else { + // Any other mode boots normally: drop a request left over from an + // earlier on_next_reset() call. + adaboot_clear_update_request(); } - // int err_code = sd_power_gpregret_set(0, DFU_MAGIC_UF2_RESET); - // if (err_code != NRF_SUCCESS) { - // // Set it without the soft device if the SD failed. (It may be off.) - // nrf_power_gpregret_set(NRF_POWER, new_value); - // } if (runmode == RUNMODE_SAFE_MODE) { safe_mode_on_next_reset(SAFE_MODE_PROGRAMMATIC); } diff --git a/ports/zephyr-cp/common-hal/nvm/ByteArray.c b/ports/zephyr-cp/common-hal/nvm/ByteArray.c index b8f552d6773..a1b76a2806c 100644 --- a/ports/zephyr-cp/common-hal/nvm/ByteArray.c +++ b/ports/zephyr-cp/common-hal/nvm/ByteArray.c @@ -38,6 +38,10 @@ static bool ensure_nvm_open(void) { } uint32_t common_hal_nvm_bytearray_get_length(const nvm_bytearray_obj_t *self) { + // The nvm partition size comes from the devicetree, so it's only known at + // runtime. The singleton object's len field is never initialized; look it + // up from the opened flash area instead. + (void)self; if (!ensure_nvm_open()) { return 0; } diff --git a/ports/zephyr-cp/common-hal/nvm/ByteArray.h b/ports/zephyr-cp/common-hal/nvm/ByteArray.h index 9c771aaa3a9..97014de905b 100644 --- a/ports/zephyr-cp/common-hal/nvm/ByteArray.h +++ b/ports/zephyr-cp/common-hal/nvm/ByteArray.h @@ -10,4 +10,6 @@ typedef struct { mp_obj_base_t base; + uint8_t *start_address; + uint32_t len; } nvm_bytearray_obj_t; diff --git a/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.c b/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.c index d36b571535a..07ac4fa1634 100644 --- a/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.c +++ b/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.c @@ -12,6 +12,7 @@ #include "py/runtime.h" #include +#include #include #include #include @@ -27,8 +28,8 @@ static void incrementalencoder_gpio_callback(const struct device *port, return; } - int a = gpio_pin_get(self->pin_a->port, self->pin_a->number); - int b = gpio_pin_get(self->pin_b->port, self->pin_b->number); + int a = gpio_pin_get(self->port_a, self->number_a); + int b = gpio_pin_get(self->port_b, self->number_b); if (a < 0 || b < 0) { return; } @@ -36,6 +37,17 @@ static void incrementalencoder_gpio_callback(const struct device *port, shared_module_softencoder_state_update(self, new_state); } +// Runs an iobroker/GPIO call and, on failure, releases any partial setup +// before raising a Python exception with the Zephyr errno. +#define CHECK_RESULT_OR_DEINIT(x) \ + do { \ + int _res = (x); \ + if (_res < 0) { \ + common_hal_rotaryio_incrementalencoder_deinit(self); \ + raise_zephyr_error(_res); \ + } \ + } while (0) + void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencoder_obj_t *self, const mcu_pin_obj_t *pin_a, const mcu_pin_obj_t *pin_b) { // Ensure object starts in its deinit state. @@ -45,60 +57,35 @@ void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencode self->pin_b = pin_b; self->divisor = 4; - if (!device_is_ready(pin_a->port) || !device_is_ready(pin_b->port)) { - common_hal_rotaryio_incrementalencoder_deinit(self); - raise_zephyr_error(-ENODEV); - } + // Claim both pins in the iobroker module so that bus allocations + // refuse them while this object holds them. The calls also resolve the + // GPIO controller devices and pin numbers from the pins' global numbers; + // they are kept in the object for every later pad operation. + CHECK_RESULT_OR_DEINIT(iobroker_gpio_allocate(pin_a->package_pin, &self->port_a, &self->number_a)); + CHECK_RESULT_OR_DEINIT(iobroker_gpio_allocate(pin_b->package_pin, &self->port_b, &self->number_b)); - int result = gpio_pin_configure(pin_a->port, pin_a->number, GPIO_INPUT | GPIO_PULL_UP); - if (result != 0) { - common_hal_rotaryio_incrementalencoder_deinit(self); - raise_zephyr_error(result); - } + CHECK_RESULT_OR_DEINIT(device_is_ready(self->port_a) && device_is_ready(self->port_b) ? 0 : -ENODEV); - result = gpio_pin_configure(pin_b->port, pin_b->number, GPIO_INPUT | GPIO_PULL_UP); - if (result != 0) { - common_hal_rotaryio_incrementalencoder_deinit(self); - raise_zephyr_error(result); - } + CHECK_RESULT_OR_DEINIT(gpio_pin_configure(self->port_a, self->number_a, GPIO_INPUT | GPIO_PULL_UP)); + CHECK_RESULT_OR_DEINIT(gpio_pin_configure(self->port_b, self->number_b, GPIO_INPUT | GPIO_PULL_UP)); self->callback_a.encoder = self; gpio_init_callback(&self->callback_a.callback, incrementalencoder_gpio_callback, - BIT(pin_a->number)); - result = gpio_add_callback(pin_a->port, &self->callback_a.callback); - if (result != 0) { - common_hal_rotaryio_incrementalencoder_deinit(self); - raise_zephyr_error(result); - } + BIT(self->number_a)); + CHECK_RESULT_OR_DEINIT(gpio_add_callback(self->port_a, &self->callback_a.callback)); self->callback_b.encoder = self; gpio_init_callback(&self->callback_b.callback, incrementalencoder_gpio_callback, - BIT(pin_b->number)); - result = gpio_add_callback(pin_b->port, &self->callback_b.callback); - if (result != 0) { - common_hal_rotaryio_incrementalencoder_deinit(self); - raise_zephyr_error(result); - } - - result = gpio_pin_interrupt_configure(pin_a->port, pin_a->number, GPIO_INT_EDGE_BOTH); - if (result != 0) { - common_hal_rotaryio_incrementalencoder_deinit(self); - raise_zephyr_error(result); - } + BIT(self->number_b)); + CHECK_RESULT_OR_DEINIT(gpio_add_callback(self->port_b, &self->callback_b.callback)); - result = gpio_pin_interrupt_configure(pin_b->port, pin_b->number, GPIO_INT_EDGE_BOTH); - if (result != 0) { - common_hal_rotaryio_incrementalencoder_deinit(self); - raise_zephyr_error(result); - } + CHECK_RESULT_OR_DEINIT(gpio_pin_interrupt_configure(self->port_a, self->number_a, GPIO_INT_EDGE_BOTH)); + CHECK_RESULT_OR_DEINIT(gpio_pin_interrupt_configure(self->port_b, self->number_b, GPIO_INT_EDGE_BOTH)); - int a = gpio_pin_get(pin_a->port, pin_a->number); - int b = gpio_pin_get(pin_b->port, pin_b->number); + int a = gpio_pin_get(self->port_a, self->number_a); + int b = gpio_pin_get(self->port_b, self->number_b); uint8_t quiescent_state = ((uint8_t)(a > 0) << 1) | (uint8_t)(b > 0); shared_module_softencoder_state_init(self, quiescent_state); - - claim_pin(pin_a); - claim_pin(pin_b); } bool common_hal_rotaryio_incrementalencoder_deinited(rotaryio_incrementalencoder_obj_t *self) { @@ -111,14 +98,22 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o } // Best-effort cleanup. During failed construct(), some of these may not be - // initialized yet. Ignore cleanup errors. - gpio_pin_interrupt_configure(self->pin_a->port, self->pin_a->number, GPIO_INT_DISABLE); - gpio_pin_interrupt_configure(self->pin_b->port, self->pin_b->number, GPIO_INT_DISABLE); - gpio_remove_callback(self->pin_a->port, &self->callback_a.callback); - gpio_remove_callback(self->pin_b->port, &self->callback_b.callback); - - reset_pin(self->pin_a); - reset_pin(self->pin_b); + // initialized yet. Ignore cleanup errors. The pad operations are only run + // when both claims were taken, so that the devices are valid. + if (self->port_a != NULL && self->port_b != NULL) { + gpio_pin_interrupt_configure(self->port_a, self->number_a, GPIO_INT_DISABLE); + gpio_pin_interrupt_configure(self->port_b, self->number_b, GPIO_INT_DISABLE); + gpio_remove_callback(self->port_a, &self->callback_a.callback); + gpio_remove_callback(self->port_b, &self->callback_b.callback); + } + if (self->port_a != NULL) { + (void)iobroker_gpio_release(self->port_a, self->number_a); + self->port_a = NULL; + } + if (self->port_b != NULL) { + (void)iobroker_gpio_release(self->port_b, self->number_b); + self->port_b = NULL; + } common_hal_rotaryio_incrementalencoder_mark_deinit(self); } @@ -126,4 +121,6 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o void common_hal_rotaryio_incrementalencoder_mark_deinit(rotaryio_incrementalencoder_obj_t *self) { self->pin_a = NULL; self->pin_b = NULL; + self->port_a = NULL; + self->port_b = NULL; } diff --git a/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.h b/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.h index a0d2bb392e2..55ab26c65d4 100644 --- a/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.h +++ b/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.h @@ -22,6 +22,12 @@ struct rotaryio_incrementalencoder_obj { mp_obj_base_t base; const mcu_pin_obj_t *pin_a; const mcu_pin_obj_t *pin_b; + // GPIO controller devices and pin numbers within them, resolved from the + // pins' global numbers by the gpio allocate calls at construct time. + const struct device *port_a; + gpio_pin_t number_a; + const struct device *port_b; + gpio_pin_t number_b; rotaryio_incrementalencoder_gpio_callback_t callback_a; rotaryio_incrementalencoder_gpio_callback_t callback_b; uint8_t state; // diff --git a/ports/zephyr-cp/common-hal/rtc/RTC.c b/ports/zephyr-cp/common-hal/rtc/RTC.c new file mode 100644 index 00000000000..2e4c93ebc21 --- /dev/null +++ b/ports/zephyr-cp/common-hal/rtc/RTC.c @@ -0,0 +1,90 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "shared/timeutils/timeutils.h" +#include "shared-bindings/rtc/RTC.h" +#include "common-hal/rtc/RTC.h" + +#include "bindings/zephyr_kernel/__init__.h" + +#include +#include +#include + +// The RTC driver already initializes its device at Zephyr boot time, so there +// is nothing for the port to start up: time is simply read from and written +// to the devicetree `rtc` device. +static const struct device *const rtc_dev = DEVICE_DT_GET(DT_ALIAS(rtc)); + +// Zephyr stores calibration in parts per billion. CircuitPython's calibration +// unit is approximately one part per million. +#define CALIBRATION_PPM_TO_ZEPHYR (1000) + +void common_hal_rtc_get_time(timeutils_struct_time_t *tm) { + struct rtc_time zephyr_tm; + int ret = rtc_get_time(rtc_dev, &zephyr_tm); + if (ret == -ENODATA) { + // The RTC has never been set (no battery-backed hardware and no + // seeded time). Report the start of the CircuitPython epoch, + // 2000-01-01, instead of failing; users set it with + // rtc.RTC().datetime. + timeutils_seconds_since_2000_to_struct_time(0, tm); + return; + } + CHECK_ZEPHYR_RESULT(ret); + + tm->tm_year = zephyr_tm.tm_year + 1900; + tm->tm_mon = zephyr_tm.tm_mon + 1; + tm->tm_mday = zephyr_tm.tm_mday; + tm->tm_hour = zephyr_tm.tm_hour; + tm->tm_min = zephyr_tm.tm_min; + tm->tm_sec = zephyr_tm.tm_sec; + // tm_wday and tm_yday are filled in by struct_time_from_tm. +} + +void common_hal_rtc_set_time(timeutils_struct_time_t *tm) { + struct rtc_time zephyr_tm = { + .tm_year = tm->tm_year - 1900, + .tm_mon = tm->tm_mon - 1, + .tm_mday = tm->tm_mday, + .tm_hour = tm->tm_hour, + .tm_min = tm->tm_min, + .tm_sec = tm->tm_sec, + }; + CHECK_ZEPHYR_RESULT(rtc_set_time(rtc_dev, &zephyr_tm)); +} + +int common_hal_rtc_get_calibration(void) { + #ifdef CONFIG_RTC_CALIBRATION + int32_t zephyr_calibration; + int ret = rtc_get_calibration(rtc_dev, &zephyr_calibration); + if (ret == -ENOSYS || ret == -ENOTSUP) { + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); + } + CHECK_ZEPHYR_RESULT(ret); + return zephyr_calibration / CALIBRATION_PPM_TO_ZEPHYR; + #else + // The Zephyr RTC calibration syscall only exists with + // CONFIG_RTC_CALIBRATION, which is off unless a driver needs it. + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); + #endif +} + +void common_hal_rtc_set_calibration(int calibration) { + #ifdef CONFIG_RTC_CALIBRATION + int ret = rtc_set_calibration(rtc_dev, (int32_t)calibration * CALIBRATION_PPM_TO_ZEPHYR); + if (ret == -ENOSYS || ret == -ENOTSUP) { + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); + } + CHECK_ZEPHYR_RESULT(ret); + #else + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); + #endif +} diff --git a/ports/zephyr-cp/common-hal/rtc/RTC.h b/ports/zephyr-cp/common-hal/rtc/RTC.h new file mode 100644 index 00000000000..083d1d052c7 --- /dev/null +++ b/ports/zephyr-cp/common-hal/rtc/RTC.h @@ -0,0 +1,12 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +// The RTC device is selected by the devicetree `rtc` alias, following +// Zephyr's own RTC sample (samples/drivers/rtc). The rtc module is only +// enabled (CIRCUITPY_RTC=1) when the alias exists and CONFIG_RTC is on, so +// common-hal/rtc/RTC.c can always DEVICE_DT_GET(DT_ALIAS(rtc)). diff --git a/ports/renode/background.h b/ports/zephyr-cp/common-hal/rtc/__init__.c similarity index 63% rename from ports/renode/background.h rename to ports/zephyr-cp/common-hal/rtc/__init__.c index 3ff2509eb4a..a24f54f1cdd 100644 --- a/ports/renode/background.h +++ b/ports/zephyr-cp/common-hal/rtc/__init__.c @@ -1,7 +1,5 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries // // SPDX-License-Identifier: MIT - -#pragma once diff --git a/ports/zephyr-cp/cptools/build_all_boards.py b/ports/zephyr-cp/cptools/build_all_boards.py index 8505e732efe..bab09e8fb42 100755 --- a/ports/zephyr-cp/cptools/build_all_boards.py +++ b/ports/zephyr-cp/cptools/build_all_boards.py @@ -14,6 +14,7 @@ import concurrent.futures import os import pathlib +import select import shlex import subprocess import sys @@ -34,6 +35,11 @@ def acquire(self): return except InterruptedError: continue + except BlockingIOError: + # GNU make opens its jobserver pipe non-blocking, so reading when + # no token is available raises instead of blocking. Wait until a + # token is written or the pipe closes. + select.select([self.read_fd], [], []) def release(self): while True: @@ -42,6 +48,9 @@ def release(self): return except InterruptedError: continue + except BlockingIOError: + # Defensive: a non-blocking pipe can also refuse writes when full. + select.select([], [self.write_fd], []) def pass_fds(self): return (self.read_fd, self.write_fd) diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 8594c245c81..90e36d6c310 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -114,7 +114,7 @@ # Other flags to set when a module is enabled EXTRA_FLAGS = { "audiobusio": {"AUDIOBUSIO_I2SOUT": 1, "AUDIOBUSIO_PDMIN": 0}, - "busio": {"BUSIO_SPI": 1, "BUSIO_I2C": 1}, + "busio": {"BUSIO_SPI": 1, "BUSIO_I2C": 1, "BUSIO_UART": 1}, "rotaryio": {"ROTARYIO_SOFTENCODER": 1}, "synthio": {"SYNTHIO_MAX_CHANNELS": 12}, } @@ -168,20 +168,25 @@ async def preprocess_and_split_defs(compiler, source_file, build_path, flags): ) +def _concatenate_if_changed(inputs, output_file): + """Write the concatenation of inputs to output_file, but only if it differs from what is + already there, so an unchanged output keeps its modification time.""" + content = "".join(path.read_text() for path in inputs) + if not output_file.exists() or output_file.read_text() != content: + output_file.write_text(content) + + async def collect_defs(mode, build_path): output_file = build_path / f"{mode}defs.collected" splitdir = build_path / "genhdr" / mode - to_collect = list(splitdir.glob(f"**/*.{mode}")) - batch_size = 50 - await cpbuild.run_command( - ["cat", "-s", *to_collect[:batch_size], ">", output_file], - splitdir, + # Sorted so the output does not depend on directory order. + to_collect = sorted(splitdir.glob(f"**/*.{mode}")) + await cpbuild.run_function( + _concatenate_if_changed, + (to_collect, output_file), + {}, + description=f"Collect {mode} definitions -> {output_file.relative_to(build_path)}", ) - for i in range(0, len(to_collect), batch_size): - await cpbuild.run_command( - ["cat", "-s", *to_collect[i : i + batch_size], ">>", output_file], - splitdir, - ) return output_file @@ -189,9 +194,13 @@ async def generate_qstr_headers(build_path, compiler, flags, translation): collected = await collect_defs("qstr", build_path) generated = build_path / "genhdr" / "qstrdefs.generated.h" + # check_hash: the collected file is rewritten on every build, so this reruns every + # build too. Keep the old modification time when the header comes out unchanged, or + # every file that includes it would be recompiled. await cpbuild.run_command( ["python", srcdir / "py" / "makeqstrdata.py", collected, ">", generated], srcdir, + check_hash=[generated], ) compression_level = 9 @@ -230,29 +239,21 @@ async def generate_qstr_headers(build_path, compiler, flags, translation): async def generate_module_header(build_path): collected = await collect_defs("module", build_path) + generated = build_path / "genhdr" / "moduledefs.h" await cpbuild.run_command( - [ - "python", - srcdir / "py" / "makemoduledefs.py", - collected, - ">", - build_path / "genhdr" / "moduledefs.h", - ], + ["python", srcdir / "py" / "makemoduledefs.py", collected, ">", generated], srcdir, + check_hash=[generated], ) async def generate_root_pointer_header(build_path): collected = await collect_defs("root_pointer", build_path) + generated = build_path / "genhdr" / "root_pointers.h" await cpbuild.run_command( - [ - "python", - srcdir / "py" / "make_root_pointers.py", - collected, - ">", - build_path / "genhdr" / "root_pointers.h", - ], + ["python", srcdir / "py" / "make_root_pointers.py", collected, ">", generated], srcdir, + check_hash=[generated], ) @@ -316,6 +317,10 @@ def determine_enabled_modules(board_info, portdir, srcdir): enabled_modules.add("storage") module_reasons["storage"] = "Zephyr board has flash" + if board_info.get("nvm_size", 0) > 0: + enabled_modules.add("nvm") + module_reasons["nvm"] = "Board has NVM partition" + network_enabled = board_info.get("wifi", False) or board_info.get("hostnetwork", False) if network_enabled: @@ -330,13 +335,19 @@ def determine_enabled_modules(board_info, portdir, srcdir): enabled_modules.add("ssl") module_reasons["ssl"] = "Zephyr networking enabled" - for port_module in (portdir / "bindings").iterdir(): + # Iterate the shared-bindings directory in a stable (sorted) order. Several + # modules can enable the same reverse dependency, and the "reason" comment + # recorded in autogen_board_info.toml belongs to whichever module enabled it + # first. Directory iteration order is filesystem dependent (it differs + # between machines and CI runners), so an unsorted walk made the generated + # comments - and therefore the committed toml files - change between builds. + for port_module in sorted((portdir / "bindings").iterdir(), key=lambda x: x.name): if not board_info.get(port_module.name, False): continue enabled_modules.add(port_module.name) module_reasons[port_module.name] = f"Zephyr board has {port_module.name}" - for shared_module in (srcdir / "shared-bindings").iterdir(): + for shared_module in sorted((srcdir / "shared-bindings").iterdir(), key=lambda x: x.name): if not board_info.get(shared_module.name, False) or not shared_module.glob("*.c"): continue enabled_modules.add(shared_module.name) @@ -366,14 +377,16 @@ async def build_circuitpython(): # noqa: C901 board = cmake_args["BOARD_ALIAS"] if not board: board = zephyr_board - translation = cmake_args["TRANSLATION"] - if not translation: - translation = "en_US" + # The environment wins, so the Makefile can switch languages without a reconfigure. + translation = os.environ.get("TRANSLATION") or cmake_args["TRANSLATION"] or "en_US" for module in ALWAYS_ON_MODULES: circuitpython_flags.append(f"-DCIRCUITPY_{module.upper()}=1") lto = cmake_args.get("LTO", "n") == "y" circuitpython_flags.append(f"-DCIRCUITPY_ENABLE_MPY_NATIVE={1 if enable_mpy_native else 0}") circuitpython_flags.append(f"-DCIRCUITPY_FULL_BUILD={1 if full_build else 0}") + # Zephyr does not reset all pins in bulk on soft reset, so never_reset is + # compiled out and hardware is released by GC finalizers instead. + circuitpython_flags.append("-DCIRCUITPY_BULK_RESET=0") circuitpython_flags.append("-DCIRCUITPY_OPT_LOAD_ATTR_FAST_PATH=1") circuitpython_flags.append(f"-DCIRCUITPY_OPT_MAP_LOOKUP_CACHE={1 if full_build else 0}") circuitpython_flags.append(f"-DCIRCUITPY_SETTINGS_TOML={1 if full_build else 0}") @@ -434,6 +447,16 @@ async def build_circuitpython(): # noqa: C901 autogen_board_info_fn = mpconfigboard_fn.parent / "autogen_board_info.toml" + # Filesystem type is a compile-time choice made by the partition layout: + # zephyr2cp reports littlefs when the devicetree has a littlefs_partition + # node, otherwise the supervisor mounts FAT. + filesystem_littlefs = board_info.get("littlefs", False) + circuitpython_flags.append( + f"-DCIRCUITPY_FILESYSTEM_LITTLEFS={1 if filesystem_littlefs else 0}" + ) + if filesystem_littlefs: + circuitpython_flags.append("-DMICROPY_VFS_LFS2=1") + creator_id = mpconfigboard.get("CIRCUITPY_CREATOR_ID", mpconfigboard.get("USB_VID", 0x1209)) creation_id = mpconfigboard.get("CIRCUITPY_CREATION_ID", mpconfigboard.get("USB_PID", 0x000C)) circuitpython_flags.append(f"-DCIRCUITPY_CREATOR_ID=0x{creator_id:08x}") @@ -460,6 +483,7 @@ async def build_circuitpython(): # noqa: C901 "extmod/vfs_fat.c", "lib/tlsf/tlsf.c", portdir / "background.c", + portdir / "supervisor/adaboot_info.c", portdir / "common-hal/microcontroller/__init__.c", portdir / "common-hal/microcontroller/Pin.c", portdir / "common-hal/microcontroller/Processor.c", @@ -501,6 +525,16 @@ async def build_circuitpython(): # noqa: C901 supervisor_source.extend(top.glob("supervisor/shared/web_workflow/*.c")) usb_ok = board_info.get("usb_device", False) + if usb_ok and filesystem_littlefs: + # USB MSC can only expose a FAT block image; a littlefs CIRCUITPY has + # no MSC-compatible layout and USB MSC of it would corrupt the drive. + # Rename the filesystem partition to fatfs_partition in the board's + # Adaboot layout dtsi, or remove the USB device from the devicetree. + raise SystemExit( + f"{board}: littlefs CIRCUITPY filesystem is incompatible with USB. " + "Rename the littlefs_partition node to fatfs_partition in the board's " + "layout dtsi, or disable the USB device controller in the devicetree." + ) circuitpython_flags.append(f"-DCIRCUITPY_USB_DEVICE={1 if usb_ok else 0}") if usb_ok: @@ -623,15 +657,6 @@ async def build_circuitpython(): # noqa: C901 for library_source in LIBRARY_SOURCE[module.name]: library_sources.extend(top.glob(library_source)) - if os.environ.get("CI", "false") == "true": - # Warn if it isn't up to date. - if ( - not autogen_board_info_fn.exists() - or autogen_board_info_fn.read_text() != tomlkit.dumps(autogen_board_info) - ): - logger.warning( - f"autogen_board_info.toml is missing or out of date. Please run `make BOARD={board}` locally and commit {autogen_board_info_fn}." - ) autogen_modules.add(tomlkit.comment("extmod modules shared with MicroPython")) for extmod_module in EXTMOD_MODULES: enabled = extmod_module in enabled_modules @@ -642,9 +667,36 @@ async def build_circuitpython(): # noqa: C901 flag_name = MODULE_FLAG_NAMES.get(extmod_module, extmod_module.upper()) circuitpython_flags.append(f"-DCIRCUITPY_{flag_name}={1 if enabled else 0}") + if os.environ.get("CI", "false") == "true": + # Warn if it isn't up to date. + if ( + not autogen_board_info_fn.exists() + or autogen_board_info_fn.read_text() != tomlkit.dumps(autogen_board_info) + ): + logger.warning( + f"autogen_board_info.toml is missing or out of date. Please run `make BOARD={board}` locally and commit {autogen_board_info_fn}." + ) + # Also as an annotation, so it shows on the run and next to the file in the + # pull request rather than in one board log out of 29. The board builds once + # per language, so only the first build of it says anything. + reported = builddir / "autogen_board_info.reported" + if not reported.exists(): + reported.touch() + print( + f"::warning file={autogen_board_info_fn.relative_to(srcdir)}::" + f"out of date, run `make BOARD={board}` and commit it", + flush=True, + ) + if autogen_board_info_fn.parent.exists(): autogen_board_info_fn.write_text(tomlkit.dumps(autogen_board_info)) + # Set NVM size from the board's NVM partition. + nvm_size = board_info.get("nvm_size", 0) + circuitpython_flags.append(f"-DCIRCUITPY_INTERNAL_NVM_SIZE={nvm_size}") + if nvm_size > 0: + circuitpython_flags.append("-DCIRCUITPY_INTERNAL_NVM_START_ADDR=0") + for mpflag in MPCONFIG_FLAGS: enabled = mpflag in DEFAULT_MODULES circuitpython_flags.append(f"-DCIRCUITPY_{mpflag.upper()}={1 if enabled else 0}") @@ -670,6 +722,23 @@ async def build_circuitpython(): # noqa: C901 ) source_files = supervisor_source + hal_source + ["extmod/vfs.c"] + if filesystem_littlefs: + source_files.extend( + ( + "extmod/vfs_lfs.c", + "lib/littlefs/lfs2.c", + "lib/littlefs/lfs2_util.c", + ) + ) + circuitpython_flags.extend( + ( + "-DLFS2_NO_MALLOC", + "-DLFS2_NO_DEBUG", + "-DLFS2_NO_WARN", + "-DLFS2_NO_ERROR", + "-DLFS2_NO_ASSERT", + ) + ) if ulab_enabled: source_files.extend(sorted((top / "extmod" / "ulab" / "code").rglob("*.c"))) assembly_files = [] diff --git a/ports/zephyr-cp/cptools/check_partitions.py b/ports/zephyr-cp/cptools/check_partitions.py index 05fb982f4a1..92016eaaac3 100644 --- a/ports/zephyr-cp/cptools/check_partitions.py +++ b/ports/zephyr-cp/cptools/check_partitions.py @@ -341,23 +341,54 @@ def compare(label, want): if expected["circuitpy"] == "external": # supervisor/flash.c uses the first flash device no partition covers as the # drive, which is the whole chip only while the chip carries no partitions. - if "circuitpy_partition" in partitions: - problems.append( - f"circuitpy_partition: declared, but {counterpart} uses the whole " - f"external flash as the drive" - ) + # An explicit single partition covering the whole chip is equivalent -- + # it pins the drive to the same area the dynamic-area fallback would + # claim -- so allow exactly that: one external partition starting at + # offset 0. Anything else on external flash (extra partitions, or a + # partition that doesn't start at 0) would not be claimed by the + # fallback and breaks the drive. external = [node for node in edt.nodes if is_external_flash(node)] if not external: problems.append(f"no external flash, but {counterpart} keeps the drive on one") - for dev, offset, size in partitions.values(): - if is_external_flash(dev): + external_partitions = [ + (dev, offset, size) + for dev, offset, size in partitions.values() + if is_external_flash(dev) + ] + whole_chip_ok = len(external_partitions) == 1 and external_partitions[0][1] == 0 + if external_partitions and not whole_chip_ok: + dev = external_partitions[0][0] + problems.append( + f"{dev.labels[0] if dev.labels else dev.name}: carries partitions, " + f"but {counterpart} uses the whole chip as the drive" + ) + else: + # The partition is on internal flash. The fork names it + # fatfs_partition (boards with native USB) or littlefs_partition (no + # USB); the older CircuitPython port layout named it circuitpy_partition. + # supervisor/flash.c accepts all three via FIXED_PARTITION_EXISTS. + want = expected["circuitpy"] + fs_labels = ("circuitpy_partition", "fatfs_partition", "littlefs_partition") + found = next( + (partitions[label] for label in fs_labels if label in partitions), + None, + ) + if found is None: + problems.append( + f"circuitpy_partition: missing, {counterpart} has it at " + f"0x{want[0]:x}+0x{want[1]:x}" + ) + else: + dev, offset, size = found + if not is_internal_flash(dev): problems.append( - f"{dev.labels[0] if dev.labels else dev.name}: carries partitions, " - f"but {counterpart} uses the whole chip as the drive" + f"circuitpy_partition: not on the internal flash, unlike {counterpart}" + ) + if (offset, size) != want: + problems.append( + f"circuitpy_partition: 0x{offset:x}+0x{size:x}, {counterpart} has it at " + f"0x{want[0]:x}+0x{want[1]:x}" ) - break - else: - compare("circuitpy_partition", expected["circuitpy"]) return problems diff --git a/ports/zephyr-cp/cptools/cpbuild.py b/ports/zephyr-cp/cptools/cpbuild.py index edaf020c264..8121f4b8016 100644 --- a/ports/zephyr-cp/cptools/cpbuild.py +++ b/ports/zephyr-cp/cptools/cpbuild.py @@ -96,13 +96,27 @@ async def __aexit__(self, exc_type, exc, tb): self.new_token(token) +def _default_job_count(): + # process_cpu_count() (Python 3.13+) counts only the CPUs this process is allowed to + # run on, e.g. inside a container or under taskset. cpu_count() counts all CPUs. + if hasattr(os, "process_cpu_count"): + return os.process_cpu_count() or 1 + return os.cpu_count() or 1 + + def _create_semaphore(): match = re.search(r"fifo:([^\s]+)", os.environ.get("MAKEFLAGS", "")) fifo_path = None if match: fifo_path = match.group(1) return _MakeJobClient(fifo_path=fifo_path) - return asyncio.BoundedSemaphore(1) + # No fifo jobserver found. Either we were not run from make at all, or make is older + # than 4.4. Older make shares its jobserver as a pair of inherited file descriptors + # (--jobserver-auth=R,W in MAKEFLAGS), and only keeps them open for commands that + # start with "+" or $(MAKE); the west build command is neither, so we cannot use them. + # ubuntu-24.04 CI runners have make 4.3. Rather than compile one file at a time, + # run as many compiles as there are CPUs. + return asyncio.BoundedSemaphore(_default_job_count()) shared_semaphore = _create_semaphore() @@ -420,8 +434,13 @@ async def compile( async def archive(self, objects: list[pathlib.Path], output_file: pathlib.Path): output_file.parent.mkdir(parents=True, exist_ok=True) responsefile = output_file.with_suffix(".rsp") + # Always create the archive from scratch. `ar r` on an existing archive replaces the + # members named on its command line and keeps every other member, so after a build + # of another language the old translations-*.o and autogen_display_resources-*.o + # stayed in the archive ahead of the new ones and the linker used them. + output_file.unlink(missing_ok=True) await run_command( - [self.ar, "rvs", output_file, *objects], + [self.ar, "rcs", output_file, *objects], description=f"Create archive {output_file.relative_to(self.srcdir)}", working_directory=self.srcdir, responsefile=responsefile, diff --git a/ports/zephyr-cp/cptools/tests/test_check_partitions.py b/ports/zephyr-cp/cptools/tests/test_check_partitions.py index e8f9d470662..c6754c6930c 100644 --- a/ports/zephyr-cp/cptools/tests/test_check_partitions.py +++ b/ports/zephyr-cp/cptools/tests/test_check_partitions.py @@ -396,3 +396,175 @@ def test_parity_reports_missing_partition(self): problems = check_parity(edt, "raspberrypi/raspberry_pi_pico") assert any(p.startswith("nvm_partition: missing") for p in problems) assert any(p.startswith("circuitpy_partition: missing") for p in problems) + + # The Adaboot fork's dtsi names the filesystem partition by its backing + # store (fatfs_partition for native-USB drives, littlefs_partition for + # boards without USB); the older CircuitPython port layout named it + # circuitpy_partition. supervisor/flash.c accepts all three via + # FIXED_PARTITION_EXISTS, so check_parity must too. + + def test_parity_accepts_fatfs_partition_in_place_of_circuitpy(self): + edt = parse_dts_string(self.rp2040_layout_with_fs_label(0xFF000, "fatfs_partition")) + assert check_parity(edt, "raspberrypi/raspberry_pi_pico") == [] + + def test_parity_accepts_littlefs_partition_in_place_of_circuitpy(self): + edt = parse_dts_string(self.rp2040_layout_with_fs_label(0xFF000, "littlefs_partition")) + assert check_parity(edt, "raspberrypi/raspberry_pi_pico") == [] + + def rp2040_layout_with_fs_label(self, nvm_offset, fs_label): + return xip_flash( + f""" + code_partition: partition@100 {{ + compatible = "zephyr,mapped-partition"; + reg = <0x100 0xfdf00>; + }}; + nvm_partition: partition@{nvm_offset:x} {{ + compatible = "zephyr,mapped-partition"; + label = "nvm"; + reg = <0x{nvm_offset:x} 0x1000>; + }}; + {fs_label}: partition@100000 {{ + compatible = "zephyr,mapped-partition"; + label = "filesystem"; + reg = <0x100000 0x100000>; + }}; + """ + ) + + def nordic_with_external_drive(self, gd25_partitions): + """Internal flash + an SPI NOR gd25q16 partition layout for the nRF52 + boards. Counterpart is nordic/feather_nrf52840_express, which uses + QSPI_FLASH_FILESYSTEM=1 (the whole chip is the drive).""" + return f"""/dts-v1/; + +/ {{ + #address-cells = <1>; + #size-cells = <1>; + + soc {{ + #address-cells = <1>; + #size-cells = <1>; + ranges; + + flash0: flash@0 {{ + compatible = "soc-nv-flash"; + erase-block-size = <4096>; + reg = <0x0 0x100000>; + ranges = <0x0 0x0 0x100000>; + #address-cells = <1>; + #size-cells = <1>; + + partitions {{ + ranges; + #address-cells = <1>; + #size-cells = <1>; + + code_partition: partition@26000 {{ + compatible = "zephyr,mapped-partition"; + label = "code-partition"; + reg = <0x26000 0xc4000>; + }}; + storage_partition: partition@ea000 {{ + compatible = "zephyr,mapped-partition"; + label = "storage"; + reg = <0xea000 0x8000>; + }}; + nvm_partition: partition@f2000 {{ + compatible = "zephyr,mapped-partition"; + label = "nvm"; + reg = <0xf2000 0x2000>; + }}; + }}; + }}; + + qspi {{ + compatible = "vnd,spi"; + reg = <0x0 0x100>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + gd25q16: gd25q16@0 {{ + compatible = "jedec,spi-nor"; + reg = <0>; + size = <2097152>; + spi-max-frequency = <8000000>; + jedec-id = [c8 40 15]; + + partitions {{ + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; +{gd25_partitions} + }}; + }}; + }}; + }}; +}}; +""" + + def test_parity_accepts_explicit_whole_chip_fatfs_partition(self): + """A single fatfs_partition covering the entire external flash chip is + equivalent to the nordic-port QSPI_FLASH_FILESYSTEM=1 dynamic-area + fallback -- both pin the drive to the whole 2 MB chip -- so the parity + check must accept it.""" + edt = parse_dts_string( + self.nordic_with_external_drive( + """ + fatfs_partition: partition@0 { + label = "filesystem"; + reg = <0x0 0x200000>; + }; + """ + ) + ) + assert check_parity(edt, "nordic/feather_nrf52840_express") == [] + + def test_parity_accepts_explicit_whole_chip_littlefs_partition(self): + edt = parse_dts_string( + self.nordic_with_external_drive( + """ + littlefs_partition: partition@0 { + label = "filesystem"; + reg = <0x0 0x200000>; + }; + """ + ) + ) + assert check_parity(edt, "nordic/feather_nrf52840_express") == [] + + def test_parity_rejects_offset_external_partition(self): + """A partition starting above offset 0 on the external chip breaks the + dynamic-area fallback, so it must be reported even though it is the + only partition on the chip.""" + edt = parse_dts_string( + self.nordic_with_external_drive( + """ + fatfs_partition: partition@1000 { + label = "filesystem"; + reg = <0x1000 0x1ff000>; + }; + """ + ) + ) + problems = check_parity(edt, "nordic/feather_nrf52840_express") + assert any("gd25q16" in p for p in problems) + + def test_parity_rejects_extra_external_partitions(self): + """More than one partition on the external chip would also leave the + dynamic-area fallback unable to claim it; report it.""" + edt = parse_dts_string( + self.nordic_with_external_drive( + """ + fatfs_partition: partition@0 { + label = "filesystem"; + reg = <0x0 0x100000>; + }; + other_partition: partition@100000 { + reg = <0x100000 0x100000>; + }; + """ + ) + ) + problems = check_parity(edt, "nordic/feather_nrf52840_express") + assert any("gd25q16" in p for p in problems) diff --git a/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py b/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py index b147ae0605e..d9a8a6d6f54 100644 --- a/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py +++ b/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py @@ -15,7 +15,15 @@ sys.modules["cpbuild"] = type(sys)("cpbuild") sys.modules["cpbuild"].run_in_thread = lambda x: x -from zephyr2cp import find_flash_devices, find_ram_regions, BLOCKED_FLASH_COMPAT, MINIMUM_RAM_SIZE +import pytest + +from zephyr2cp import ( + find_flash_devices, + find_ram_regions, + BLOCKED_FLASH_COMPAT, + MINIMUM_RAM_SIZE, + add_toml_pin_names, +) def parse_dts_string(dts_content): @@ -587,3 +595,173 @@ def test_board_with_chosen_memory_region(self): assert len(rams) == 2 assert rams[0][0] == "axisram2" assert rams[1][0] == "axisram1" + + +class TestAddTomlPinNames: + """Test suite for add_toml_pin_names.""" + + def _add( + self, toml_pins, package_pins, port_indexes, ioports, pad_names=None, board_names=None + ): + board_names = dict(board_names or {}) + pad_pin = {} + pad_gpio = {} + pad_names = {} if pad_names is None else dict(pad_names) + for entry in package_pins or []: + if "pad" not in entry and "pad_name" not in entry: + # Informational module pin. + continue + pad = entry.get("pad", entry["pin"]) + pad_pin[pad] = entry["pin"] + if "gpio" in entry: + pad_gpio[pad] = entry["gpio"] + if "pad_name" in entry and pad not in pad_names: + pad_names[pad] = entry["pad_name"] + analog_board_names = add_toml_pin_names( + board_names, + {"pins": toml_pins}, + package_pins, + "nrf54l15_qfn48", + port_indexes, + ioports, + pad_names, + pad_gpio, + ) + return board_names, analog_board_names + + def test_package_pin_number(self): + package_pins = [ + {"pin": 1, "pad_name": "P1.00", "gpio": 32}, + {"pin": 5, "pad_name": "P0.07", "gpio": 7}, + ] + board_names, _ = self._add( + {"LED": 5}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {7}} + ) + assert board_names == {("gpio0", 7): ["LED"]} + + def test_ball_id(self): + package_pins = [{"pin": 12, "pad_name": "P1.15", "ball": "B2", "gpio": 47}] + board_names, _ = self._add( + {"LED": "b2"}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio1": {15}} + ) + assert board_names == {("gpio1", 15): ["LED"]} + + def test_name_sanitized(self): + package_pins = [{"pin": 3, "pad_name": "P0.00", "gpio": 0}] + board_names, _ = self._add({"boot button": 3}, package_pins, {"gpio0": 0}, {"gpio0": {0}}) + assert board_names == {("gpio0", 0): ["BOOT_BUTTON"]} + + def test_missing_package_map_raises(self): + with pytest.raises(RuntimeError, match="package pin map"): + add_toml_pin_names( + {}, + {"pins": {"LED": 1}}, + None, + "custom", + {"gpio0": 0}, + {"gpio0": {0}}, + {}, + {}, + ) + + def test_unknown_package_pin_raises(self): + package_pins = [{"pin": 1, "pad_name": "P1.00", "gpio": 32}] + with pytest.raises(RuntimeError, match="package pin 9 is not in"): + self._add({"LED": 9}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_unknown_ball_raises(self): + package_pins = [{"pin": 1, "pad_name": "P1.00", "gpio": 32}] + with pytest.raises(RuntimeError, match="ball Z9"): + self._add({"LED": "Z9"}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_bad_value_type_raises(self): + package_pins = [{"pin": 1, "pad_name": "P1.00", "gpio": 32}] + with pytest.raises(RuntimeError, match="package pin number"): + self._add({"LED": True}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_bad_name_raises(self): + package_pins = [{"pin": 1, "pad_name": "P1.00", "gpio": 32}] + with pytest.raises(RuntimeError, match="not usable"): + self._add({"1 LED": 1}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_pad_not_on_gpio_controller_raises(self): + # A pad bonded to a GPIO controller the board doesn't enable: it is + # an error. + package_pins = [{"pin": 1, "pad_name": "P1.00", "gpio": 32}] + with pytest.raises(RuntimeError, match="not on an enabled GPIO controller"): + self._add({"LED": 1}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_analog_only_pad_returns_names_by_pad(self): + # Pads whose package map entry carries no GPIO bond (analog-only + # pads like MCX N's ANA pads) return their board module names keyed + # by SoC pad; the caller creates their pin objects from the map's + # datasheet pad names. + package_pins = [{"pin": 110, "pad_name": "ANA_0"}] + board_names, analog_board_names = self._add( + {"A0": 110}, package_pins, {"gpio0": 0}, {"gpio0": {0}}, {110: "ANA_0"} + ) + assert board_names == {} + assert analog_board_names == {110: ["A0"]} + + def test_analog_pad_conflict_raises(self): + # The name is already bound to a GPIO pad by the devicetree walk, + # so [pins] cannot rebind it to an analog-only pad. + package_pins = [{"pin": 110, "pad_name": "ANA_0"}] + with pytest.raises(RuntimeError, match="already maps to gpio0 pin 0"): + self._add( + {"A0": 110}, + package_pins, + {"gpio0": 0}, + {"gpio0": {0}}, + {110: "ANA_0"}, + board_names={("gpio0", 0): ["A0"]}, + ) + + def test_no_pins_is_noop(self): + board_names = {} + analog = add_toml_pin_names( + board_names, {}, None, None, {"gpio0": 0}, {"gpio0": {0}}, {}, {} + ) + assert board_names == {} + assert analog == {} + + +class TestAddTomlPinNamesDuplicates: + """Deduplication and conflict behavior of add_toml_pin_names.""" + + PACKAGE_PINS = [ + {"pin": 5, "pad_name": "P1.04", "gpio": 36}, + {"pin": 27, "pad_name": "P0.02", "gpio": 2}, + ] + PORT_INDEXES = {"gpio0": 0, "gpio1": 1} + IOPORTS = {"gpio0": {2}, "gpio1": {4}} + + def _add(self, toml_pins, board_names=None): + board_names = dict(board_names or {}) + add_toml_pin_names( + board_names, + {"pins": toml_pins}, + self.PACKAGE_PINS, + "nrf54l15_qfn48", + self.PORT_INDEXES, + self.IOPORTS, + {e["pin"]: e["pad_name"] for e in self.PACKAGE_PINS}, + {e["pin"]: e["gpio"] for e in self.PACKAGE_PINS}, + ) + return board_names + + def test_dedupes_two_keys_same_name_same_pin(self): + board_names = self._add({"MY PIN": 5, "my-pin": 5}) + assert board_names == {("gpio1", 4): ["MY_PIN"]} + + def test_conflict_two_keys_same_name_different_pins(self): + with pytest.raises(RuntimeError, match="already maps to gpio1 pin 4"): + self._add({"MY PIN": 5, "my-pin": 27}) + + def test_dedupes_existing_devicetree_name_same_pin(self): + board_names = self._add({"green led": 5}, {("gpio1", 4): ["Green LED"]}) + assert board_names == {("gpio1", 4): ["Green LED"]} + + def test_conflict_existing_devicetree_name_different_pin(self): + with pytest.raises(RuntimeError, match="already maps to gpio0 pin 2"): + self._add({"STATUS": 5}, {("gpio0", 2): ["STATUS"]}) diff --git a/ports/zephyr-cp/cptools/zephyr2cp.py b/ports/zephyr-cp/cptools/zephyr2cp.py index cc40ccfda85..585a9d45116 100644 --- a/ports/zephyr-cp/cptools/zephyr2cp.py +++ b/ports/zephyr-cp/cptools/zephyr2cp.py @@ -1,6 +1,7 @@ import logging import pathlib import re +import tomllib import cpbuild import yaml @@ -43,6 +44,18 @@ BUSIO_CLASSES = {"serial": "UART", "i2c": "I2C", "spi": "SPI"} +# Compatibles analogio is implemented against: the SoC ADC (whose pads +# iobroker resolves to their fixed analog input), the SoC DAC (whose output +# pad is fixed per SoC) and the emulated devices. +ANALOG_COMPAT = { + "nordic_nrf_saadc": "adc", + "zephyr_adc_emul": "adc", + "vnd_dac": "dac", + "renesas_ra_dac": "dac", + "nxp_lpc_lpadac": "adc", + "nxp_lpdac": "dac", +} + AUDIOBUSIO_CLASSES = {"i2s": "I2SOut"} CONNECTORS = { @@ -328,14 +341,21 @@ def find_flash_devices(device_tree): value = device_tree.root.nodes["chosen"].props[k] path2chosen[value.to_path()] = k + # Paths of zephyr,sim-flash controllers (the flash simulator used by + # native_sim). The simulator driver defines its device on the controller + # node, not on its soc-nv-flash child, so the controller must be used and + # the child skipped. + sim_flash_controller_paths = set() + flashes = [] logger.debug("Flash devices:") # Traverse all nodes in the device tree - remaining_nodes = set([device_tree.root]) + # A list, not a set, so the nodes are visited in the same order every build. + remaining_nodes = [device_tree.root] while remaining_nodes: node = remaining_nodes.pop() - remaining_nodes.update(node.nodes.values()) + remaining_nodes.extend(node.nodes.values()) # Get compatible strings compatible = [] @@ -367,6 +387,22 @@ def find_flash_devices(device_tree): if "flash" not in drivers: continue + if compatible[0] == "zephyr,sim-flash": + # Always use the controller for the flash simulator, even when it + # is chosen as zephyr,flash-controller. + sim_flash_controller_paths.add(node.path) + if node.labels: + flashes.append(node.labels[0]) + continue + + # The soc-nv-flash child of a flash simulator has no device defined on + # it; the controller (handled above) is the flash device. + if node.parent is not None and node.parent.path in sim_flash_controller_paths: + logger.debug( + f" skipping flash {node.labels[0] if node.labels else node.name} (sim-flash child)" + ) + continue + # Skip chosen nodes because they are used by Zephyr if node in path2chosen: logger.debug( @@ -381,25 +417,6 @@ def find_flash_devices(device_tree): ) continue - # Skip soc-nv-flash nodes whose parent is itself a flash device — the - # parent is the real Zephyr device (e.g. nxp,imx-flexspi-nor) and the - # child has no driver-instantiated symbol. - if "soc-nv-flash" in compatible and node.parent is not None: - parent_compat = [] - if "compatible" in node.parent.props: - parent_compat = node.parent.props["compatible"].to_strings() - parent_drivers = [] - for c in parent_compat: - underscored = c.replace(",", "_").replace("-", "_") - d = COMPAT_TO_DRIVER.get(underscored) or MANUAL_COMPAT_TO_DRIVER.get(underscored) - if d: - parent_drivers.append(d) - if "flash" in parent_drivers: - logger.debug( - f" skipping flash {node.labels[0] if node.labels else node.name} (parent is flash device)" - ) - continue - if node.labels: flashes.append(node.labels[0]) @@ -433,7 +450,8 @@ def find_ram_regions(device_tree): rams.append((label, "z_mapped_end", _label_to_end(label), size, chosen.path)) # Traverse all nodes in the device tree to find memory-region nodes - remaining_nodes = set([device_tree.root]) + # A list, not a set, so the nodes are visited in the same order every build. + remaining_nodes = [device_tree.root] while remaining_nodes: node = remaining_nodes.pop() @@ -450,7 +468,7 @@ def find_ram_regions(device_tree): if node == chosen: continue - remaining_nodes.update(node.nodes.values()) + remaining_nodes.extend(node.nodes.values()) if "compatible" not in node.props or not node.labels: continue @@ -487,6 +505,37 @@ def find_ram_regions(device_tree): INPUT_KEY_NAMES = {} +# Mask selecting the nRF pin number field (absolute pin, port*32+pin) of +# a pinctrl psel entry. The pin control entry uses all-ones in this field to +# mark a disconnected signal (NRF_PIN_DISCONNECTED). +NRF_PIN_FIELD_MASK = 0x1FF + + +def _pinctrl_default_psels(node): + """Return the raw nRF psel entries of a node's "default" pinctrl state. + + The state node (referenced by pinctrl-0) groups its configuration in + child nodes (typically named group1, group2, ...) that each carry a + psels property. + + Returns None when the node does not use pinctrl. + """ + prop = node.props.get("pinctrl-0") + if prop is None: + return None + psels = [] + try: + for state in prop.to_nodes(): + for group in state.nodes.values(): + if "psels" not in group.props: + continue + for value in group.props["psels"].to_nums(): + psels.append(value) + except (dtlib.DTError, KeyError): + return None + return psels + + def _populate_input_key_names(): header = ( pathlib.Path(__file__).parent.parent @@ -509,6 +558,162 @@ def _populate_input_key_names(): _populate_input_key_names() +def add_toml_pin_names( + board_names, + mpconfigboard, + package_pins, + package_choice, + port_indexes, + ioports, + pad_name_of_pad, + gpio_pad_of_pad, +): + """Add board pin names from circuitpython.toml's ``[pins]`` table. + + Each entry maps a board module name to a package pin number or, for ball + grid array packages, the datasheet's ball id (e.g. ``"A1"``). Entries are + resolved to SoC pads with the iobroker package pin map selected by the + ``IOBROKER_PACKAGE`` choice, and appended to ``board_names`` like + devicetree-derived names are. + + A name that already maps to the same pin (from the devicetree walk or an + earlier entry) is deduplicated; a name that would map to a different pin + than an existing entry is a build error. + + Pads whose package map entry carries no GPIO bond (the map's + analog-only pads) cannot key ``board_names``, whose entries describe + GPIO controller pins; the names collected for them are keyed by SoC pad + and returned as a ``pad -> board module names`` dict instead, with the + pin objects created from the map's datasheet pad names (e.g. + "ANA_0") by the caller. + """ + toml_pins = (mpconfigboard or {}).get("pins") + if not toml_pins: + return {} + if package_pins is None: + reason = "NONE" if package_choice == "none" else "unset" + raise RuntimeError( + f"circuitpython.toml [pins] needs an iobroker package pin map but " + f"CONFIG_IOBROKER_PACKAGE is {reason}" + ) + port_label_of_index = {index: label for label, index in port_indexes.items()} + + # Board module names for the package map's analog-only pads, keyed by + # their SoC pad (a pad no enabled GPIO controller covers). + analog_board_names = {} + + def sanitize(name): + return name.upper().replace(" ", "_").replace("-", "_").replace("(", "").replace(")", "") + + # Package pin -> pad (the SoC pad number; SoC packages default it to + # the pin), ball id -> pad, for resolving [pins] values. + package_pin_to_pad = {} + balls = {} + for package_pin_entry in package_pins: + pad = package_pin_entry.get("pad", None) + if pad is None and "pad_name" not in package_pin_entry: + # Informational module pin (e.g. bonded to GND): no pad. + continue + pin = package_pin_entry["pin"] + if pad is None: + pad = pin + if pin in package_pin_to_pad and package_pin_to_pad[pin] != pad: + raise RuntimeError(f"package pin {pin} maps to multiple pads in the package pin map") + package_pin_to_pad[pin] = pad + if "ball" in package_pin_entry: + ball = package_pin_entry["ball"] + if ball in balls and balls[ball] != pad: + raise RuntimeError(f"ball {ball} maps to multiple pads in the package pin map") + balls[ball] = pad + + # Names already in use from the devicetree walk, as sanitized name -> + # list of pins it is attached to. + pins_of_name = {} + for pin_key, names in board_names.items(): + for existing_name in names: + pins = pins_of_name.setdefault(sanitize(existing_name), []) + if pin_key not in pins: + pins.append(pin_key) + for name, package_pin in toml_pins.items(): + board_name = sanitize(name) + if not re.match(r"^[A-Z][A-Z0-9_]*$", board_name): + raise RuntimeError( + f"circuitpython.toml [pins] name {name!r} is not usable as a board module name" + ) + if isinstance(package_pin, str): + ball = package_pin.strip().upper() + pad = balls.get(ball) + if pad is None: + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: ball {package_pin} is not in " + f"the package pin map" + ) + elif isinstance(package_pin, int) and not isinstance(package_pin, bool): + pad = package_pin_to_pad.get(package_pin) + if pad is None: + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: package pin {package_pin} is " + f"not in the package pin map" + ) + else: + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: value must be a package pin number " + f"or ball id" + ) + gpio = gpio_pad_of_pad.get(pad) + if gpio is None: + # A pad whose package map entry carries no GPIO bond (an + # analog-only pad): the pin object is created by the caller + # from the map's datasheet pad name. + # A pad no enabled GPIO controller covers (an analog-only pad): + # the pin object is created by the caller from the map's + # datasheet pad name. + if pad not in pad_name_of_pad: + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: package pin {package_pin} is " + f"on pad {pad}, which has no GPIO bond and no pad name in the " + f"package pin map" + ) + previous = pins_of_name.get(board_name, []) + if previous and pad not in previous: + previous = ", ".join( + f"{label} pin {num}" if isinstance(label, str) else f"pad {label}" + for label, num in previous + ) + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: already maps to {previous}, " + f"but [pins] assigns it to analog pad {pad}" + ) + if previous: + # Same name already attached to this pad (devicetree or an + # earlier entry); nothing to add. + continue + pins_of_name[board_name] = [pad] + analog_board_names.setdefault(pad, []).append(board_name) + continue + label = port_label_of_index.get(gpio // 32) + if label is None or gpio % 32 not in ioports.get(label, []): + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: pad {pad} bonds gpio pad " + f"{gpio}, which is not on an enabled GPIO controller" + ) + pin_key = (label, gpio % 32) + previous_pins = pins_of_name.get(board_name, []) + if previous_pins and pin_key not in previous_pins: + previous = ", ".join(f"{label} pin {num}" for label, num in previous_pins) + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: already maps to {previous}, " + f"but [pins] assigns it to {label} pin {gpio % 32}" + ) + if previous_pins: + # Same name already attached to this pin (devicetree or an + # earlier entry); nothing to add. + continue + pins_of_name[board_name] = [pin_key] + board_names.setdefault(pin_key, []).append(board_name) + return analog_board_names + + @cpbuild.run_in_thread def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfigboard=None): # noqa: C901 board_dir = builddir / "board" @@ -524,6 +729,9 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig config_bt_enabled = False config_bt_found = False + config_adc_enabled = False + config_dac_enabled = False + config_rtc_enabled = False config_present = True config = zephyrbuilddir / ".config" if not config.exists(): @@ -533,11 +741,15 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig if line.startswith("CONFIG_BT="): config_bt_enabled = line.strip().endswith("=y") config_bt_found = True - break - if line.startswith("# CONFIG_BT is not set"): + elif line.startswith("# CONFIG_BT is not set"): config_bt_enabled = False config_bt_found = True - break + elif line.startswith("CONFIG_ADC="): + config_adc_enabled = line.strip().endswith("=y") + elif line.startswith("CONFIG_DAC="): + config_dac_enabled = line.strip().endswith("=y") + elif line.startswith("CONFIG_RTC="): + config_rtc_enabled = line.strip().endswith("=y") runners = zephyrbuilddir / "runners.yaml" runners = yaml.safe_load(runners.read_text()) @@ -593,6 +805,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig status_led = None status_led_inverted = False boot_button = None + boot_button_active_high = False path2chosen = {} chosen2path = {} @@ -604,6 +817,8 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig active_zephyr_devices = {} usb_num_endpoint_pairs = 0 ble_hardware_present = False + adc_present = False + dac_present = False for k in device_tree.root.nodes["chosen"].props: value = device_tree.root.nodes["chosen"].props[k] path2chosen[value.to_path()] = k @@ -616,10 +831,11 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig board_info["zephyr_display"] = True board_info["displayio"] = True - remaining_nodes = set([device_tree.root]) + # A list, not a set, so the nodes are visited in the same order every build. + remaining_nodes = [device_tree.root] while remaining_nodes: node = remaining_nodes.pop() - remaining_nodes.update(node.nodes.values()) + remaining_nodes.extend(node.nodes.values()) gpio = node.props.get("gpio-controller", False) gpio_map = node.props.get("gpio-map", []) status = node.props.get("status", None) @@ -681,6 +897,19 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig if driver not in active_zephyr_devices: active_zephyr_devices[driver] = [] active_zephyr_devices[driver].append(node.labels) + elif driver in ("adc", "dac"): + # Analog peripherals: analogio is only enabled for the + # compatibles it implements (the SoC ADCs iobroker can resolve + # to their fixed analog inputs, and the emulated devices); + # external sensor ADC chips are not pad-addressable. + if underscored in ANALOG_COMPAT: + if driver == "adc": + adc_present = True + else: + dac_present = True + logger.info(f"Supported analog driver: {underscored}") + else: + logger.debug(f"Analog driver without analogio support: {underscored}") else: logger.warning(f"Unsupported driver: {driver}") @@ -691,9 +920,23 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig ngpios = 32 all_ioports.append(node.labels[0]) if status == "okay": - ioports[node.labels[0]] = set(range(0, ngpios)) + ioports[node.labels[0]] = range(0, ngpios) if gpio_map and compatible and compatible[0] != "gpio-nexus": - connector_pins = CONNECTORS.get(compatible[0], None) + # Per-board connector names from circuitpython.toml's + # ``[connectors.]`` table take precedence. They key the + # gpio-map position (the header pin number as a string) to a name + # or a list of names, so boards whose silkscreen differs from the + # generic per-compatible list can supply their own. + connector_override = None + if node.labels: + connector_override = ( + (mpconfigboard or {}).get("connectors", {}).get(node.labels[0]) + ) + connector_pins = ( + connector_override + if connector_override is not None + else CONNECTORS.get(compatible[0], None) + ) if connector_pins is None: logger.warning(f"Unsupported connector mapping compatible: {compatible[0]}") else: @@ -701,16 +944,25 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig for offset, t, label in gpio_map._markers: if not label: continue - if i >= len(connector_pins): - logger.warning( - f"Connector mapping for {compatible[0]} has more pins than names; " - f"stopping at {len(connector_pins)}" - ) - break num = int.from_bytes(gpio_map.value[offset + 4 : offset + 8], "big") + if isinstance(connector_pins, dict): + pin_entry = connector_pins.get(str(i)) + if pin_entry is None: + logger.debug( + f"Connector {node.labels[0]} position {i} has no name; skipping" + ) + i += 1 + continue + else: + if i >= len(connector_pins): + logger.warning( + f"Connector mapping for {compatible[0]} has more pins than names; " + f"stopping at {len(connector_pins)}" + ) + break + pin_entry = connector_pins[i] if (label, num) not in board_names: board_names[(label, num)] = [] - pin_entry = connector_pins[i] if isinstance(pin_entry, list): board_names[(label, num)].extend(pin_entry) else: @@ -742,6 +994,8 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig props = key_node.props ioport = props["gpios"]._markers[1][2] num = int.from_bytes(props["gpios"].value[4:8], "big") + flags = int.from_bytes(props["gpios"].value[8:12], "big") + active_high = not (flags & GPIO_ACTIVE_LOW) if (ioport, num) not in board_names: board_names[(ioport, num)] = [] @@ -761,11 +1015,13 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig # The sw0 alias designates the conventional first user # button, so prefer it as the boot button. boot_button = (ioport, num) + boot_button_active_high = active_high board_names[(ioport, num)].extend(aliases) # Default to the first button in device tree order when no sw0 # alias has designated one yet. if boot_button is None: boot_button = (ioport, num) + boot_button_active_high = active_high if len(all_ioports) > 1: a, b = all_ioports[:2] @@ -787,22 +1043,172 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig pin_declarations = ["#pragma once"] mcu_pin_mapping = [] board_pin_mapping = [] + # Hardware port index of each GPIO controller; it defines the global pin + # numbering (index * 32 + pin) that pin objects and the iobroker module + # both use. On nRF SoCs the index comes from the label digits (gpio0 -> + # port 0, gpio6 -> port 6). Virtual GPIO controllers, such as the + # infineon,cyw43-gpio on the Pico 2 W, get the lowest unused index so they + # never collide with a numbered controller. + port_indexes = {} + used_indexes = set() + for label in sorted(ioports.keys()): + match = re.match(r"^gpio(\d+)$", label) + if match: + port_indexes[label] = int(match.group(1)) + used_indexes.add(port_indexes[label]) + for label in sorted(ioports.keys()): + if label in port_indexes: + continue + index = 0 + while index in used_indexes: + index += 1 + port_indexes[label] = index + used_indexes.add(index) + # Package pin map selected through the IOBROKER_PACKAGE choice: the pin + # objects, their package pins and the reserved pads all come from the + # iobroker package data. Pads are numbered by the package's pin ids + # (row-major ball order, or the datasheet's pin number for a QFN/QFP + # package); a pad bonded to a GPIO controller carries its global GPIO + # number (port index * 32 + pin). The 1:1 choice is an identity map (pad + # == package pin == GPIO number); IOBROKER_PACKAGE_NONE has no map, so + # every pin object gets IOBROKER_NO_PIN. + package_pin_of_pad = {} + gpio_pad_of_pad = {} + # Datasheet pad name of every mapped pad (e.g. "P0.02", "ANA_0"): the + # pin objects take their names from these. + pad_name_of_pad = {} + package_pins = None + package_choice = None + if config_present: + for line in config.read_text().splitlines(): + stripped = line.strip() + if not stripped.startswith("CONFIG_IOBROKER_PACKAGE_") or not stripped.endswith("=y"): + continue + package_choice = stripped[len("CONFIG_IOBROKER_PACKAGE_") : -len("=y")].lower() + break + if package_choice == "one_to_one": + # Identity map over the enabled GPIO controllers. + package_pins = [] + for ioport in sorted(ioports.keys()): + for num in ioports[ioport]: + global_number = port_indexes[ioport] * 32 + num + pin_object_name = f"P{ioport[len(shared_prefix) :].upper()}_{num:02d}" + package_pins.append( + { + "pin": global_number, + "pad": global_number, + "pad_name": pin_object_name, + "gpio": global_number, + } + ) + package_pin_of_pad[global_number] = global_number + gpio_pad_of_pad[global_number] = global_number + pad_name_of_pad[global_number] = pin_object_name + elif package_choice not in (None, "none"): + package_toml = ( + pathlib.Path(__file__).resolve().parent.parent + / "internal-modules" + / "iobroker" + / "packages" + / f"{package_choice}.toml" + ) + with package_toml.open("rb") as f: + package = tomllib.load(f) + package_pins = package["pins"] + for package_pin_entry in package_pins: + if "pad_name" not in package_pin_entry and "pad" not in package_pin_entry: + # Informational module pin (e.g. bonded to GND): it + # carries no SoC pad, so it maps nothing. + continue + pad = package_pin_entry.get("pad", package_pin_entry["pin"]) + package_pin_of_pad[pad] = package_pin_entry["pin"] + if "pad_name" in package_pin_entry: + pad_name_of_pad[pad] = package_pin_entry["pad_name"] + if "gpio" in package_pin_entry: + gpio_pad_of_pad[pad] = package_pin_entry["gpio"] + # Pads no package pin names (GPIO pads whose controller is missing from + # the map or pads the board enables beyond the package): they get pin + # objects named after the GPIO controller with an IOBROKER_NO_PIN + # package pin, like boards without an iobroker package always had. + # Board pin names from circuitpython.toml: ``[pins]`` maps a board module + # name to a package pin number or ball id, resolved to a SoC pad with + # the package pin map above. This is independent of Zephyr's devicetree + # labels and aliases. Pads with no GPIO bond (the map's analog-only + # pads) get their names back keyed by pad; their pin objects are + # created below from the map's datasheet pad names. + analog_board_names = add_toml_pin_names( + board_names, + mpconfigboard, + package_pins, + package_choice, + port_indexes, + ioports, + pad_name_of_pad, + gpio_pad_of_pad, + ) + # GPIO pad number -> pad: every (label, num) name and alias the + # devicetree walk produced keys into one pin object per pad. + pad_of_gpio = {} + for pad, gpio in gpio_pad_of_pad.items(): + if gpio in pad_of_gpio and pad_of_gpio[gpio] != pad: + logger.warning( + f"package pin maps {pad_of_gpio[gpio]} and {pad} both bond gpio pad {gpio}" + ) + continue + pad_of_gpio[gpio] = pad + # Pin object names, deduplicated: two pads with the same datasheet name + # would produce two MP_QSTR bindings for one name. + pin_name_of_pad = {} + seen_pin_names = {} + for pad, name in pad_name_of_pad.items(): + pin_object_name = ( + name.upper() + .replace(".", "_") + .replace(" ", "_") + .replace("-", "_") + .replace("(", "") + .replace(")", "") + ) + if not re.match(r"^[A-Z][A-Z0-9_]*$", pin_object_name): + logger.warning( + f"pad {pad} has unusable pad name {name!r} in the package " + f"pin map; its pin object is skipped" + ) + continue + if pin_object_name in seen_pin_names: + raise RuntimeError( + f"package pin map: pads {seen_pin_names[pin_object_name]} and {pad} " + f"both carry pad name {name!r}" + ) + seen_pin_names[pin_object_name] = pad + pin_name_of_pad[pad] = pin_object_name for ioport in sorted(ioports.keys()): for num in ioports[ioport]: - pin_object_name = f"P{ioport[len(shared_prefix) :].upper()}_{num:02d}" + gpio = port_indexes[ioport] * 32 + num + pad = pad_of_gpio.get(gpio) + if pad is not None: + pin_object_name = pin_name_of_pad.get(pad) + if pin_object_name is None: + continue + package_pin = package_pin_of_pad[pad] + package_pin_init = str(package_pin) + else: + # Unbonded GPIO pad: no package pin; keep the object for + # devicetree names, with a disconnected package pin. + pin_object_name = f"P{ioport[len(shared_prefix) :].upper()}_{num:02d}" + package_pin_init = "IOBROKER_NO_PIN" if status_led and (ioport, num) == status_led: status_led = pin_object_name if boot_button and (ioport, num) == boot_button: boot_button = pin_object_name pin_defs.append( - f"const mcu_pin_obj_t pin_{pin_object_name} = {{ .base.type = &mcu_pin_type, .port = DEVICE_DT_GET(DT_NODELABEL({ioport})), .number = {num}}};" + f"const mcu_pin_obj_t pin_{pin_object_name} = {{ .base.type = &mcu_pin_type, .package_pin = {package_pin_init}}};" ) pin_declarations.append(f"extern const mcu_pin_obj_t pin_{pin_object_name};") mcu_pin_mapping.append( f"{{ MP_ROM_QSTR(MP_QSTR_{pin_object_name}), MP_ROM_PTR(&pin_{pin_object_name}) }}," ) board_pin_names = board_names.get((ioport, num), []) - for board_pin_name in board_pin_names: board_pin_name = ( board_pin_name.upper() @@ -815,11 +1221,44 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig f"{{ MP_ROM_QSTR(MP_QSTR_{board_pin_name}), MP_ROM_PTR(&pin_{pin_object_name}) }}," ) + # Pin objects for the package map's analog-only pads (pads with no GPIO + # bond, e.g. MCX N's ANA pads), keyed by pad: they are named by their + # datasheet pad name and reach the outside world only through the + # analog APIs, which take package pins. + for pad in sorted(analog_board_names): + pin_object_name = pin_name_of_pad.get(pad) + if pin_object_name is None: + continue + pin_defs.append( + f"const mcu_pin_obj_t pin_{pin_object_name} = {{ .base.type = &mcu_pin_type, .package_pin = {package_pin_of_pad[pad]}}};" + ) + pin_declarations.append(f"extern const mcu_pin_obj_t pin_{pin_object_name};") + mcu_pin_mapping.append( + f"{{ MP_ROM_QSTR(MP_QSTR_{pin_object_name}), MP_ROM_PTR(&pin_{pin_object_name}) }}," + ) + for board_pin_name in analog_board_names[pad]: + board_pin_mapping.append( + f"{{ MP_ROM_QSTR(MP_QSTR_{board_pin_name}), MP_ROM_PTR(&pin_{pin_object_name}) }}," + ) + pin_defs = "\n".join(pin_defs) pin_declarations = "\n".join(pin_declarations) board_pin_mapping = "\n ".join(board_pin_mapping) mcu_pin_mapping = "\n ".join(mcu_pin_mapping) + # Bus instances the board enabled with all-disconnected pins are routed to + # arbitrary pins at runtime by busio objects instead of being exposed as + # fixed board.X() singletons. + iobroker_labels = set() + for driver in BUSIO_CLASSES: + for labels in active_zephyr_devices.get(driver, []): + node = device_tree.label2node[labels[0]] + psels = _pinctrl_default_psels(node) + if psels is not None and all( + (value & NRF_PIN_FIELD_MASK) == NRF_PIN_FIELD_MASK for value in psels + ): + iobroker_labels.add(labels[0]) + zephyr_binding_headers = [] zephyr_binding_objects = [] zephyr_binding_labels = [] @@ -857,6 +1296,10 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig if found_main: break for labels in instances: + if labels[0] in iobroker_labels: + # Dynamically routable instances are not exposed as board + # singletons; construct a busio object with pins instead. + continue instance_name = f"{driver.replace('/', '_')}_{labels[0]}" c_function_name = f"_{instance_name}" singleton_ptr = f"{c_function_name}_singleton" @@ -897,6 +1340,165 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig zephyr_binding_objects = "\n".join(zephyr_binding_objects) zephyr_binding_labels = "\n".join(zephyr_binding_labels) + # Generate tables of allocatable bus instances for the iobroker + # Zephyr module (dynamic pin routing; nRF SoCs). Instances enabled with + # all-disconnected pinctrl can be routed to arbitrary pins at runtime; + # instances with fixed devicetree pins are only usable when the requested + # pins match their state. + pinctrl_nrf = False + if config_present: + for line in config.read_text().splitlines(): + if line.startswith("CONFIG_PINCTRL_NRF="): + pinctrl_nrf = line.strip().endswith("=y") + break + + iobroker_includes = """ +#include +#include +""" + + iobroker_tables = "" + table_parts = [] + + # Map GPIO controller devices to their hardware port index. The indexes + # define the global pin numbering shared by the pin objects and the + # iobroker module, which resolves a global number back to the + # controller device and pin within it. Boards without GPIO controllers + # generate an empty table so that gpio_split() returns -EINVAL. + if ioports: + devices = ", ".join( + f"DEVICE_DT_GET(DT_NODELABEL({label}))" for label in sorted(ioports.keys()) + ) + indexes = ", ".join(str(port_indexes[label]) for label in sorted(ioports.keys())) + count = len(port_indexes) + else: + devices = "NULL" + indexes = "0" + count = 0 + table_parts.append( + f""" +const struct device * const iobroker_gpio_port_devices[] = {{ {devices} }}; +const uint8_t iobroker_gpio_port_indexes[] = {{ {indexes} }}; +const size_t iobroker_gpio_port_count = {count}; +""" + ) + + if pinctrl_nrf: + pool_kinds = (("i2c", "i2c"), ("spi", "spi"), ("serial", "uart")) + bus_table_parts = [] + for driver, pool in pool_kinds: + dynamic_entries = [] + fixed_entries = [] + for labels in active_zephyr_devices.get(driver, []): + node = device_tree.label2node[labels[0]] + if node in path2chosen: + # Console and other system devices are not allocatable. + continue + psels = _pinctrl_default_psels(node) + if psels is None or len(psels) > 4: + continue + if all((value & NRF_PIN_FIELD_MASK) == NRF_PIN_FIELD_MASK for value in psels): + dynamic_entries.append((labels[0], None, 0)) + else: + fixed_entries.append((labels[0], psels, len(psels))) + + entries = dynamic_entries + fixed_entries + + # Always define all three pools, even when empty: iobroker.c and + # the nRF routing code reference every pool's tables whenever + # CONFIG_PINCTRL_NRF is on, so an empty pool is still an empty + # array plus a zero count. + if not entries: + bus_table_parts.append( + "const iobroker_instance_t" + f" iobroker_{pool}_buses[] = {{}};\n" + "iobroker_state_t" + f" iobroker_{pool}_bus_states[ARRAY_SIZE(iobroker_{pool}_buses)];\n" + "const size_t" + f" iobroker_{pool}_bus_count = ARRAY_SIZE(iobroker_{pool}_buses);" + ) + continue + + entry_lines = [] + psel_arrays = [] + declares = [] + for label, psels, count in entries: + declares.append(f"PINCTRL_DT_DEV_CONFIG_DECLARE(DT_NODELABEL({label}));") + entry = ( + f" {{ .dev = DEVICE_DT_GET(DT_NODELABEL({label})), " + f".pcfg = PINCTRL_DT_DEV_CONFIG_GET(DT_NODELABEL({label}))" + ) + if psels is not None: + values = ", ".join(hex(value) for value in psels) + psel_arrays.append( + f"static const pinctrl_soc_pin_t cp_{label}_dt_psels[] = {{ {values} }};" + ) + entry += f", .dt_psels = cp_{label}_dt_psels, .dt_psel_count = {count}" + entry += " }," + entry_lines.append(entry) + + bus_table_parts.append( + "\n".join(declares) + + "\n\n" + + "\n".join(psel_arrays) + + f"\nconst iobroker_instance_t iobroker_{pool}_buses[] = {{\n" + + "\n".join(entry_lines) + + "\n};\n" + + f"iobroker_state_t iobroker_{pool}_bus_states[ARRAY_SIZE(iobroker_{pool}_buses)];\n" + + f"const size_t iobroker_{pool}_bus_count = ARRAY_SIZE(iobroker_{pool}_buses);" + ) + + iobroker_tables = ( + "#if defined(CONFIG_PINCTRL)\n" + "#include \n" + + "\n\n".join(bus_table_parts) + + "\n#endif // CONFIG_PINCTRL" + ) + + if pinctrl_nrf: + # Pads claimed at boot by fixed peripherals (console UART, flash + # instance, I2S, ...): their devicetree pinctrl "default" state points + # at real pads. iobroker reports these pads as always in use so that + # allocate() rejects requests for them with -EBUSY instead of + # re-routing pads that something else is already driving. Dynamically + # routable instances have all-disconnected default states and + # contribute nothing here. The pads are iobroker package pads: the + # devicetree psels carry global GPIO numbers, so translate through + # the package map; pads without a package pin stay unrestricted + # (they are unreachable through the package pin APIs). + reserved_pads = set() + for instances in active_zephyr_devices.values(): + for labels in instances: + node = device_tree.label2node[labels[0]] + psels = _pinctrl_default_psels(node) + if psels is None: + continue + for value in psels: + gpio = value & NRF_PIN_FIELD_MASK + if gpio == NRF_PIN_FIELD_MASK: + continue + pad = pad_of_gpio.get(gpio) + if pad is not None: + reserved_pads.add(pad) + # Always emit the table, even when empty: iobroker.c references it + # whenever CONFIG_PINCTRL_NRF is on, so an empty set is still an + # empty array plus a zero count. + pads = ", ".join(str(pad) for pad in sorted(reserved_pads)) + reserved_table = ( + "const uint16_t iobroker_reserved_pads[] = { " + pads + " };\n" + "const size_t iobroker_reserved_pads_count = " + f"{len(reserved_pads)};" + ) + iobroker_tables = ( + "#if defined(CONFIG_PINCTRL)\n" + "#include \n" + + "\n\n".join(bus_table_parts + [reserved_table]) + + "\n#endif // CONFIG_PINCTRL" + ) + + if table_parts: + iobroker_tables = iobroker_tables + "\n\n" + "\n\n".join(table_parts) + # Generate i2sout_reset() that stops all board I2SOut instances if i2sout_instance_names: stop_calls = "\n ".join( @@ -958,8 +1560,13 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig status_led_inverted = "" if boot_button: boot_button = f"#define CIRCUITPY_BOOT_BUTTON (&pin_{boot_button})\n" + boot_button_active_high = ( + f"#define CIRCUITPY_BOOT_BUTTON_ACTIVE_HIGH " + f"({'1' if boot_button_active_high else '0'})\n" + ) else: boot_button = "" + boot_button_active_high = "" ram_list = [] ram_externs = [] max_size = 0 @@ -990,6 +1597,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig {status_led} {status_led_inverted} {boot_button} +{boot_button_active_high} """ if not header.exists() or header.read_text() != new_header_content: header.write_text(new_header_content) @@ -1024,6 +1632,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig #include "py/mphal.h" {zephyr_binding_headers} +{iobroker_includes} {zephyr_display_header} const struct device* const flashes[] = {{ {", ".join(flashes)} }}; @@ -1038,6 +1647,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig {pin_defs} {zephyr_binding_objects} +{iobroker_tables} {zephyr_display_object} {i2sout_reset_func} @@ -1059,7 +1669,12 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); """ - board_c.write_text(new_board_c_content) + # Only write board.c when it has changed. Rewriting it on every build gives it a new + # modification time even if the content is the same, and cpbuild then recompiles it, + # regenerates qstrdefs.generated.h from its qstrs, and recompiles every file that + # includes that header, which is all of them. + if not board_c.exists() or board_c.read_text() != new_board_c_content: + board_c.write_text(new_board_c_content) if ble_hardware_present: if not config_present: raise RuntimeError( @@ -1075,10 +1690,39 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig board_info["cflags"] = ("-I", board_dir) board_info["flash_count"] = len(flashes) board_info["rotaryio"] = bool(ioports) + # analogio requires both the devicetree device and the Zephyr driver: + # the ADC path resolves pads to their analog inputs through iobroker and + # the DAC path needs the emulated DAC for its counter tracks. + board_info["analogio"] = (adc_present and config_adc_enabled) or ( + dac_present and config_dac_enabled + ) + board_info["rtc"] = False + # RTC needs both a devicetree `rtc` alias pointing at the RTC device + # (the same selection Zephyr's own RTC sample uses) and the Zephyr RTC + # driver layer enabled (CONFIG_RTC). + rtc_node = device_tree.alias2node.get("rtc") + if rtc_node is not None: + rtc_status = rtc_node.props.get("status") + rtc_present = rtc_status is None or rtc_status.to_string() == "okay" + if rtc_present and config_rtc_enabled: + board_info["rtc"] = True + elif rtc_present: + logger.warning( + "Devicetree has a status-okay 'rtc' alias but CONFIG_RTC is " + "not enabled, so the rtc module is disabled." + ) board_info["usb_num_endpoint_pairs"] = usb_num_endpoint_pairs - # Detect NVM partition from the device tree. + # Detect NVM partition size from the device tree. + nvm_size = 0 nvm_node = device_tree.label2node.get("nvm_partition") - board_info["nvm"] = nvm_node is not None + if nvm_node and "reg" in nvm_node.props: + nvm_size = nvm_node.props["reg"].to_nums()[1] + board_info["nvm_size"] = nvm_size + + # The user filesystem type is a compile-time choice made by the partition + # layout: a littlefs_partition node (named for littlefs in the Adaboot + # fork's layout dtsi) mounts littlefs; everything else mounts FAT. + board_info["littlefs"] = device_tree.label2node.get("littlefs_partition") is not None return board_info diff --git a/ports/zephyr-cp/debug.conf b/ports/zephyr-cp/debug.conf index 2f12db119f8..21e82985247 100644 --- a/ports/zephyr-cp/debug.conf +++ b/ports/zephyr-cp/debug.conf @@ -35,6 +35,7 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 CONFIG_MAIN_STACK_SIZE=32768 CONFIG_ISR_STACK_SIZE=4096 CONFIG_UDC_DWC2_STACK_SIZE=4096 +CONFIG_UDC_RPI_PICO_STACK_SIZE=2048 CONFIG_UDC_NRF_THREAD_STACK_SIZE=4096 CONFIG_USBD_THREAD_STACK_SIZE=4096 CONFIG_USBD_MSC_STACK_SIZE=4096 diff --git a/ports/zephyr-cp/internal-modules/circuitpython-kconfig/Kconfig b/ports/zephyr-cp/internal-modules/circuitpython-kconfig/Kconfig new file mode 100644 index 00000000000..4efe8c3fcc7 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/circuitpython-kconfig/Kconfig @@ -0,0 +1,277 @@ +# CircuitPython defaults that must merge before Zephyr's subsystem Kconfig +# files. +# +# Zephyr merges this file via its module mechanism (Kconfig.zephyr sources +# "modules/Kconfig", which pulls in every module's Kconfig) right after the +# SoC/board defconfigs and BEFORE kernel/, drivers/, lib/ and subsys/. Kconfig +# takes the first `default` it sees for a symbol, so defaults defined here win +# over the ones Zephyr's own subsystem Kconfigs define later. +# +# These same defaults must NOT live in the application Kconfig +# (ports/zephyr-cp/Kconfig): it is sourced after all of Zephyr, so any symbol +# Zephyr gives a (conditional) default of its own would silently shadow the +# port's value there. Examples that were shadowed here before this module +# existed: BT_BUF_* (Zephyr defaults them for BT_CLASSIC/BT_MESH/... boards), +# BT_DEVICE_NAME_MAX, BT_L2CAP_TX_MTU, MCUMGR_TRANSPORT_NETBUF_SIZE. +# +# Boards can override any of these in their boards///board.conf. + +# ===== Peripheral defaults — enabled by default, boards can disable ===== + +config I2C + default y + +config SPI + default y + +config SPI_ASYNC + default y + +config I2S + default y + +config UART_LINE_CTRL + default y + +config ENTROPY_GENERATOR + default y + +# ===== Dynamic pin routing (nRF) ===== +# CircuitPython routes peripherals to pins at runtime on nRF SoCs. That needs +# pinctrl states to be swappable (PINCTRL_DYNAMIC) and devices to be +# de-initializable/re-initializable (DEVICE_DEINIT_SUPPORT). Both cost a little +# RAM (pinctrl configs move out of flash) and one function pointer per device. +config PINCTRL_DYNAMIC + default y if SOC_FAMILY_NORDIC_NRF + +config DEVICE_DEINIT_SUPPORT + default y if SOC_FAMILY_NORDIC_NRF + +# ===== Bluetooth defaults ===== + +# Use a variable for the chosen name so the comma isn't parsed as an argument separator +DT_BT_HCI_CHOSEN := zephyr,bt-hci + +config BT + default $(dt_chosen_enabled,$(DT_BT_HCI_CHOSEN)) + +config BT_PERIPHERAL + default y + +config BT_CENTRAL + default y + +config BT_BROADCASTER + default y + +config BT_OBSERVER + default y + +config BT_EXT_ADV + default y + +config BT_DEVICE_APPEARANCE_DYNAMIC + default y + +config BT_DEVICE_NAME_DYNAMIC + default y + +config BT_DEVICE_NAME_MAX + default 28 + +config BT_L2CAP_TX_MTU + default 253 + +# BT Buffers +config BT_BUF_CMD_TX_SIZE + default 255 + +config BT_BUF_EVT_RX_COUNT + default 16 + +config BT_BUF_EVT_RX_SIZE + default 255 + +config BT_BUF_ACL_TX_COUNT + default 3 + +config BT_BUF_ACL_TX_SIZE + default 251 + +config BT_BUF_ACL_RX_COUNT_EXTRA + default 1 + +config BT_BUF_ACL_RX_SIZE + default 255 + +config BT_GATT_DYNAMIC_DB + default y + +config BT_GATT_CLIENT + default y + +config BT_SMP + default y + +config BT_BONDABLE + default y + +config SETTINGS + default y + +config BT_SETTINGS + default y + +config BT_KEYS_OVERWRITE_OLDEST + default y + +# ===== OTA firmware updates over BLE (SMP/MCUmgr) ===== +# +# Boards that boot under MCUboot opt in via boards///sysbuild.conf +# setting CONFIG_BOOTLOADER_MCUBOOT, which sysbuild turns into an app-side +# CONFIG_BOOTLOADER_MCUBOOT. OTA is enabled when the app boots from slot0 under +# MCUboot and has Bluetooth; the app boots with the image upload in slot1. +# +# A slot1_partition must also exist in the final app devicetree: the mcuboot +# fork's partition layout overlays are applied there, so boards.toml-driven +# layouts count. Boards with a single-app layout (e.g. stm32wba65i_dk1, +# siwx917_dk2605a) have no slot1 to stage an upload in, and zephyr_img_mgmt +# fails to build without one, so MCUmgr defaults off for them automatically +# instead of needing a CONFIG_MCUMGR=n override in board.conf. +# +# Gating on BOOTLOADER_MCUBOOT (not just on a slot1_partition existing) keeps +# boards that link the app over the whole flash (e.g. nrf54lm20dk) from letting +# an upload erase the running image that happens to overlap slot1. + +DT_HAS_SLOT1 := $(dt_nodelabel_enabled,slot1_partition) + +config ZCBOR + default y if BT && BOOTLOADER_MCUBOOT && $(DT_HAS_SLOT1) + +config MCUMGR + default y if BT && BOOTLOADER_MCUBOOT && $(DT_HAS_SLOT1) + +config MCUMGR_TRANSPORT_BT + default y if BT && BOOTLOADER_MCUBOOT + +config MCUMGR_TRANSPORT_BT_REASSEMBLY + default y if MCUMGR_TRANSPORT_BT + +# Just Works pairing for the SMP service. The port registers no interactive +# pairing callbacks (no display/keyboard IO capability; numeric comparison +# raises NotImplementedError here), so MITM-authenticated pairing can never +# complete. Zephyr's defaults are the opposite (BT_SMP_ENFORCE_MITM=y and the +# PERM choice defaulting to MCUMGR_TRANSPORT_BT_PERM_RW_AUTHEN), which puts +# WRITE_AUTHEN on the SMP characteristics and makes every SMP write fail with +# insufficient authentication. Pair Just Works and require encryption only, +# per the OTA policy documented in the port README. +config BT_SMP_ENFORCE_MITM + default n if BT && BOOTLOADER_MCUBOOT + +# The default for the SMP service's GATT-permission choice. Zephyr's own +# default (subsys/mgmt/mcumgr/transport/Kconfig.bluetooth) is +# MCUMGR_TRANSPORT_BT_PERM_RW_AUTHEN, which puts WRITE_AUTHEN on the SMP +# characteristics and makes every SMP write fail with insufficient +# authentication. This module Kconfig is merged before Zephyr's subsystem +# Kconfigs, so this `default` (on the choice, not the symbol) wins. The +# choice itself is Zephyr's; only the default is added here. +choice MCUMGR_TRANSPORT_BT_PERM + default MCUMGR_TRANSPORT_BT_PERM_RW_ENCRYPT if MCUMGR_TRANSPORT_BT +endchoice + +# Request a fast connection interval while an upload is in progress. +config MCUMGR_TRANSPORT_BT_CONN_PARAM_CONTROL + default y if MCUMGR_TRANSPORT_BT + +config IMG_MANAGER + default y if MCUMGR_TRANSPORT_BT + +# IMG_MANAGER ("DFU image manager") depends on STREAM_FLASH, which has no +# default of its own, so enable it with the same gate as IMG_MANAGER. +config STREAM_FLASH + default y if MCUMGR_TRANSPORT_BT + +config MCUMGR_GRP_IMG + default y if IMG_MANAGER + +config MCUMGR_GRP_OS + default y if MCUMGR_TRANSPORT_BT + +# Let clients discover the SMP buffer count/size so they can use optimal +# upload chunking and window sizes (the nRF Connect Device Manager apps do +# this automatically). +config MCUMGR_GRP_OS_MCUMGR_PARAMS + default y if MCUMGR_GRP_OS + +# Fit a full ATT write (the port raises BT_L2CAP_TX_MTU to 515) plus the +# 8-byte MCUmgr BT overhead in each buffer, so one upload packet fills one +# buffer instead of needing reassembly. +config MCUMGR_TRANSPORT_NETBUF_SIZE + default 528 if MCUMGR_TRANSPORT_BT + +# Debug logging for the whole MCUmgr stack (OTA/SMP) cannot be defaulted +# from Kconfig source: the per-module log levels (CONFIG_..._LOG_LEVEL_DBG) +# are members of anonymous Kconfig choices, so neither `default` on the int +# nor `select` on a choice member overrides the built-in "Default" member. +# Instead, the OTA boards (the ones whose .config has CONFIG_MCUMGR=y, +# i.e. BT + MCUboot + slot1) set the assignments in their board.conf: +# +# CONFIG_MCUMGR_LOG_LEVEL_DBG=y +# CONFIG_MCUMGR_TRANSPORT_LOG_LEVEL_DBG=y +# CONFIG_MCUMGR_GRP_IMG_LOG_LEVEL_DBG=y +# CONFIG_MCUMGR_GRP_OS_LOG_LEVEL_DBG=y +# +# (In a board.conf the assignments are only valid while MCUmgr is enabled, +# which is exactly what the OTA gate guarantees there.) With +# CONFIG_LOG_MODE_IMMEDIATE the logs go straight out of the console backend +# (the board's console UART), interleaved with the CircuitPython output. + +# ===== Resetting into the bootloader ===== +# +# The app asks the installed bootloader for its update mode (the state a +# double-tap of the reset button produces) on reset_to_bootloader() and +# microcontroller.OnNextReset(RunMode.UF2 / RunMode.BOOTLOADER), via the +# adaboot fork's adaboot/update_mode.h helpers. Two bootloaders are in play: +# +# - The fork's adaboot (MCUboot) reads the boot-mode retention flag (io.c's +# io_detect_boot_mode()), so the app needs the retained-memory chain +# enabled; the flag region (zephyr,boot-mode, e.g. gpregret1) is provided +# by the fork's dtsi, which sysbuild applies to the application image too. +# Every board that boots via adaboot has a boot-mode region in its layout +# (a GPREGRET register on the Nordic SoCs, a retained SRAM byte elsewhere). +# - The stock Adafruit nRF52 bootloader (the Adafruit boards, which boot UF2 +# with no MCUboot domain) reads the raw GPREGRET register instead, so no +# retention subsystem is needed for them. + +DT_BOOT_MODE_CHOSEN := zephyr,boot-mode +DT_BOOT_INFO_CHOSEN := zephyr,bootloader-info + +config RETAINED_MEM + default y if BOOTLOADER_MCUBOOT && ($(dt_chosen_enabled,$(DT_BOOT_MODE_CHOSEN)) || $(dt_chosen_enabled,$(DT_BOOT_INFO_CHOSEN))) + +config RETENTION + default y if RETAINED_MEM + +config RETENTION_BOOT_MODE + default y if RETENTION && $(dt_chosen_enabled,$(DT_BOOT_MODE_CHOSEN)) + +# Read the boot state (mode/running slot/version) the bootloader leaves in +# the zephyr,bootloader-info shared-data region, and teach Zephyr's SMP +# bootloader-info command the fork's identity: without a hook, the +# application-side handler reports the hardcoded "MCUboot" and derives the +# mode from the app's own Kconfig. The hook answers in +# supervisor/adaboot_info.c. +config RETENTION_BOOTLOADER_INFO + default y if BOOTLOADER_MCUBOOT && $(dt_chosen_enabled,$(DT_BOOT_INFO_CHOSEN)) + +config RETENTION_BOOTLOADER_INFO_TYPE_MCUBOOT + default y if RETENTION_BOOTLOADER_INFO + +config MCUMGR_MGMT_NOTIFICATION_HOOKS + default y if MCUMGR && RETENTION_BOOTLOADER_INFO_TYPE_MCUBOOT + +config MCUMGR_GRP_OS_BOOTLOADER_INFO + default y if MCUMGR + +config MCUMGR_GRP_OS_BOOTLOADER_INFO_HOOK + default y if MCUMGR_MGMT_NOTIFICATION_HOOKS diff --git a/ports/zephyr-cp/internal-modules/circuitpython-kconfig/zephyr/module.yml b/ports/zephyr-cp/internal-modules/circuitpython-kconfig/zephyr/module.yml new file mode 100644 index 00000000000..b0e6a3b5105 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/circuitpython-kconfig/zephyr/module.yml @@ -0,0 +1,9 @@ +# Zephyr module descriptor for the defaults module. +# +# The module is loaded from the CircuitPython tree via ZEPHYR_EXTRA_MODULES in +# ports/zephyr-cp/CMakeLists.txt. It is Kconfig-only: it exists so its +# Kconfig merges before Zephyr's subsystem Kconfig files (see Kconfig here for +# why that ordering matters). +name: defaults +build: + kconfig: Kconfig diff --git a/ports/zephyr-cp/internal-modules/iobroker/CMakeLists.txt b/ports/zephyr-cp/internal-modules/iobroker/CMakeLists.txt new file mode 100644 index 00000000000..0bc5223d010 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/CMakeLists.txt @@ -0,0 +1,106 @@ +# iobroker Zephyr module build. +# +# The core is always compiled so that callers (CircuitPython's common-hal +# code) can include and call the module directly on +# every SoC. SoC-specific routing implementations are added under +# src///; without one the allocate functions report -ENOSYS. + +zephyr_library() +# SoC-agnostic core: package pin resolution, GPIO claims, pin-in-use. +zephyr_library_sources(src/iobroker.c) + +# SoC implementations live in src///. The nRF implementation +# covers every nRF SoC (they share one pinctrl encoding), so the +# level is the nRF family. Other vendors add their own directories and a +# corresponding zephyr_library_sources_ifdef() line here. +zephyr_library_sources_ifdef(CONFIG_PINCTRL_NRF + src/nordic/nrf/iobroker_route.c +) + +# Analog pad allocation: the nRF SAADC, the Renesas RA ADC/DAC and the +# emulated ADC/DAC devices used on native_sim. Other SoCs' implementations go +# into src/// and get a corresponding line here. +zephyr_library_sources_ifdef(CONFIG_ADC_NRFX_SAADC + src/nordic/nrf/iobroker_analog.c +) +if(CONFIG_DAC_RENESAS_RA) + zephyr_library_sources(src/renesas/ra/iobroker_analog.c) +endif() +if(CONFIG_ADC_EMUL OR CONFIG_DAC_TEST) + zephyr_library_sources(src/emul/iobroker_analog.c) +endif() +# The NXP MCX N LPADC/LPDAC implementation: covers both the two LPADC units +# and the LPDAC output. +if(CONFIG_ADC_MCUX_LPADC OR CONFIG_DAC_MCUX_LPDAC) + zephyr_library_sources(src/nxp/mcxn/iobroker_analog.c) +endif() +# Private headers shared by the core and the vendor implementations. +zephyr_library_include_directories(src) + +# Package pin map: render the TOML selected through the IOBROKER_PACKAGE +# choice into a build-directory translation unit. Kconfig has already run by +# the time module CMakeLists are included, so the CONFIG_ symbols are final +# here. When no reference map applies to the SoC, an empty map is generated +# instead, so package pin lookups fail cleanly with -EINVAL rather than the +# build failing to link. +set(IOBROKER_PACKAGE_CONFIGS + IOBROKER_PACKAGE_NRF54L15_QFN48 + IOBROKER_PACKAGE_NRF54LM20_CSP98 + IOBROKER_PACKAGE_NRF5340_QKAA + IOBROKER_PACKAGE_MDBT50Q_1MV2 + IOBROKER_PACKAGE_NRF52840_AQFN73 + IOBROKER_PACKAGE_RP2040_QFN56 + IOBROKER_PACKAGE_MCXN947_VDF + IOBROKER_PACKAGE_MCXN947_VNL +) +set(IOBROKER_PACKAGE_TOMLS + nrf54l15_qfn48 + nrf54lm20_csp98 + nrf5340_qkaa + mdbt50q_1mv2 + nrf52840_aqfn73 + rp2040_qfn56 + mcxn947_vdf + mcxn947_vnl +) +set(pkg_selected) +set(pkg_name) +foreach(pkg toml IN ZIP_LISTS IOBROKER_PACKAGE_CONFIGS IOBROKER_PACKAGE_TOMLS) + if(CONFIG_${pkg}) + set(pkg_selected ${pkg}) + set(pkg_name ${toml}) + endif() +endforeach() + +if(pkg_selected) + set(pkg_toml_file ${CMAKE_CURRENT_LIST_DIR}/packages/${pkg_name}.toml) + set(pkg_c_file ${CMAKE_CURRENT_BINARY_DIR}/package_pins_${pkg_name}.c) + add_custom_command(OUTPUT ${pkg_c_file} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/tools/gen_package_c.py + ${pkg_toml_file} ${pkg_c_file} + DEPENDS ${pkg_toml_file} ${CMAKE_CURRENT_LIST_DIR}/tools/gen_package_c.py + COMMENT "iobroker: generating package pin map ${pkg_name}" + ) + set_source_files_properties(${pkg_c_file} PROPERTIES GENERATED TRUE) +else() + # No package map applies to this SoC (yet): an empty map keeps the symbols + # defined so the core can report -EINVAL for every package pin lookup. + set(pkg_c_file ${CMAKE_CURRENT_BINARY_DIR}/package_pins_empty.c) + file(WRITE ${pkg_c_file} + "// Generated by the iobroker module build -- no package pin map for this SoC.\n" + "#include \n" + "const iobroker_package_pin_t iobroker_package_pins[1] = {{0, 0, IOBROKER_PAD_NO_GPIO}};\n" + "const size_t iobroker_package_pin_count = 0;\n" + ) +endif() + +# The generated file is consumed by the zephyr target from the same +# directory, so order the compilation explicitly through a custom target. +add_custom_target(iobroker_package_pins DEPENDS ${pkg_c_file}) +add_dependencies(zephyr iobroker_package_pins) +target_sources(zephyr PRIVATE ${pkg_c_file}) + +# Register the public headers app-wide (zephyr_interface) so that external +# builds that consume Zephyr's exported compile flags, such as the +# CircuitPython static library build, can include them too. +zephyr_include_directories(include) diff --git a/ports/zephyr-cp/internal-modules/iobroker/Kconfig b/ports/zephyr-cp/internal-modules/iobroker/Kconfig new file mode 100644 index 00000000000..0265248ef5c --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/Kconfig @@ -0,0 +1,53 @@ +# iobroker: dynamic peripheral allocation and runtime pin routing. +# +# The module's core is always compiled (see CMakeLists.txt); callers call it +# directly. Runtime routing needs SoC support: today it is implemented for +# nRF SoCs, whose pin control encoding can be computed at runtime and whose +# peripherals can be routed to (almost) any pin via PSEL. On other SoCs the +# allocate functions always report -ENOSYS. + +config IOBROKER_GPIO_MAX_PINS + int "Maximum number of pins allocatable for GPIO use" + default 32 + help + Size of the registry of pins claimed for plain GPIO use via + iobroker_gpio_allocate(). + +config IOBROKER_ANALOG_MAX_PINS + int "Maximum number of pins allocatable for analog use" + default 16 + help + Size of the registry of pads claimed for analog use (ADC inputs via + iobroker_adc_allocate(), DAC outputs via iobroker_dac_allocate()). + +choice IOBROKER_PACKAGE + prompt "SoC package pin map" + help + The module resolves requested package pins to SoC pads through the + package pin map selected here. Reference maps shipped with the module + live in packages/; the ONE_TO_ONE option is an identity map for boards + without a transcribed physical package, and NONE keeps the map empty so + every package pin lookup fails with -EINVAL. + +config IOBROKER_PACKAGE_ONE_TO_ONE + bool "1:1 (package pin number is the global pin number)" + help + Identity package pin map: package pin N maps to SoC pad N, where the + SoC pad is the global pin numbering (gpio port index * 32 + pin within + the port). Boards without a transcribed physical package, such as the + native simulator, use this so that pin objects can hand the iobroker + module a package pin that resolves to their pad. + +config IOBROKER_PACKAGE_NONE + bool "None (package pin lookups fail)" + help + No package pin map is available for this SoC yet. Package pin + lookups (and therefore pin claims and bus pin routing) fail with + -EINVAL until a reference map is added for the SoC. + +# Reference maps (one option per package transcribed from a SoC datasheet) +# live in Kconfig.packages; each depends on the SOC_* symbols of the SoCs the +# map applies to, so only the options valid for the selected SoC are visible. +rsource "Kconfig.packages" + +endchoice diff --git a/ports/zephyr-cp/internal-modules/iobroker/Kconfig.packages b/ports/zephyr-cp/internal-modules/iobroker/Kconfig.packages new file mode 100644 index 00000000000..d435ad97ca9 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/Kconfig.packages @@ -0,0 +1,93 @@ +# Reference package pin maps for the IOBROKER_PACKAGE choice. +# +# Generated by hand from the transcriptions in packages/*.toml (one option per +# TOML, depends on the SOC_* symbols of the SoCs the map applies to). When a +# package option is invisible the SoC does not come in that package, so the +# option cannot be selected or set from conf fragments either. +# +# Choice defaults live here too, most-specific first: the development kits' +# packages preselect the reference map for their SoC; boards with more than +# one matching package (e.g. nRF52840 modules) set their choice explicitly in +# board.conf; SoCs with no reference map fall back to the 1:1 identity map +# (package pin number == global pin number), which lets their pins resolve. + +config IOBROKER_PACKAGE_NRF54L15_QFN48 + bool "nRF54L15/10/05 QFN48 (QFAA)" + depends on SOC_NRF54L15 || SOC_NRF54L10 || SOC_NRF54L05 + help + QFN48 package pin map for the nRF54L15, nRF54L10 and nRF54L05, which + are pin-compatible. 48 pins, 31 routable GPIOs. Used by the + nRF54L15 DK. + +config IOBROKER_PACKAGE_NRF54LM20_CSP98 + bool "nRF54LM20 CSP98 (PAAA)" + depends on SOC_NRF54LM20A || SOC_NRF54LM20B + help + CSP98 (flip-chip) package pin map for the nRF54LM20A and nRF54LM20B. + 98 balls, 66 routable GPIOs, numbered sequentially in row-major order + (the datasheet labels them A1..K10). Used by the nRF54LM20 DK. + +config IOBROKER_PACKAGE_NRF5340_QKAA + bool "nRF5340 aQFN94 (QKAA)" + depends on SOC_NRF5340_CPUAPP + help + aQFN94 package pin map for the nRF5340 application core. 94 balls + (plus four corner pads and a die pad, not in the map), all 48 GPIOs + bonded, numbered sequentially in row-major order (the datasheet only + labels the balls, e.g. A17). Used by the nRF5340 DK and the nRF7002 DK. + +config IOBROKER_PACKAGE_MDBT50Q_1MV2 + bool "Raytac MDBT50Q-1MV2 module" + depends on SOC_NRF52840_QIAA + help + Pin map for the Raytac MDBT50Q-1MV2 module, which carries the + nRF52840 aQFN73 inside. 'pin' numbers the module's 61 castellated + pins as the datasheet numbers them and 'pad' remaps them to the + aQFN73 ball ids they bond to; 'gpio' carries each pad's global GPIO + number. + +config IOBROKER_PACKAGE_NRF52840_AQFN73 + bool "nRF52840 aQFN73 (NQI73)" + depends on SOC_NRF52840_QIAA + help + aQFN73 package pin map for the nRF52840. 73 balls, all 48 GPIOs + bonded, numbered sequentially in row-major order (the datasheet only + labels the balls, e.g. A8). + +config IOBROKER_PACKAGE_RP2040_QFN56 + bool "RP2040 QFN-56" + depends on SOC_RP2040 + help + QFN-56 package pin map for the RP2040. 30 user GPIOs (the package + pin map covers GPIO0..GPIO29; QSPI and power pins are omitted), + numbered as the datasheet numbers the pins. Used by the Feather + RP2040 board. + +config IOBROKER_PACKAGE_MCXN947_VDF + bool "MCXN947 VFBGA184 (VDF)" + depends on SOC_MCXN947 + help + 184-ball VFBGA package (9 x 9 mm, 0.5 mm pitch) pin map for the + MCXN947, which Zephyr orders as MCXN947VDF. Used by the + FRDM-MCXN947 board. 184 balls, 124 routable GPIOs, numbered + sequentially in row-major order (the datasheet only labels the + balls, e.g. A17). + +config IOBROKER_PACKAGE_MCXN947_VNL + bool "MCXN947 HLQFP100 (VNL)" + depends on SOC_MCXN947 + help + 100-pin HLQFP package (0.5 mm pitch) pin map for the MCXN947, + which Zephyr orders as MCXN947VNL. 100 pins, 78 routable GPIOs, + numbered as the datasheet numbers the pins. + +choice IOBROKER_PACKAGE + default IOBROKER_PACKAGE_NRF54L15_QFN48 if SOC_NRF54L15 || SOC_NRF54L10 || SOC_NRF54L05 + default IOBROKER_PACKAGE_NRF54LM20_CSP98 if SOC_NRF54LM20A || SOC_NRF54LM20B + default IOBROKER_PACKAGE_NRF5340_QKAA if SOC_NRF5340_CPUAPP_QKAA + default IOBROKER_PACKAGE_NRF52840_AQFN73 if SOC_NRF52840_QIAA + default IOBROKER_PACKAGE_RP2040_QFN56 if SOC_RP2040 + default IOBROKER_PACKAGE_MCXN947_VDF if SOC_PART_NUMBER_MCXN947VDF + default IOBROKER_PACKAGE_MCXN947_VNL if SOC_PART_NUMBER_MCXN947VNL + default IOBROKER_PACKAGE_ONE_TO_ONE +endchoice diff --git a/ports/zephyr-cp/internal-modules/iobroker/README.md b/ports/zephyr-cp/internal-modules/iobroker/README.md new file mode 100644 index 00000000000..f4cb043a79d --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/README.md @@ -0,0 +1,175 @@ +# iobroker + +A Zephyr module for **dynamic peripheral allocation and runtime pin routing**: +pick a free bus instance (I2C, SPI, UART) enabled in the devicetree, re-route +it to requested pins at runtime and hand the Zephyr device to the caller. + +Pins are specified using `package_pin_t` and represent a single pin on a package +or module containing a system-on-a-chip (SoC). This is the most common boundary +between an SoC and printed circuit board (PCB). Packages and modules may choose +to map more than one SoC pin to a package pin and this way IOBroker ensures that +each package pin is only used for one thing at a time. GPIO port and pin numbers +are often used as names for these pins but using package pins allow us to +accommodate pins without GPIO and those with multiple GPIO. + +IOBroker takes in a number of package pins and a device type. Device types are +usually `drivers/` in the zephyr source tree. For example, +`int iobroker_i2c_allocate(package_pin_t sda, package_pin_t scl, const struct device **dev_out);` +will find a Zephyr I2C device that pins sda and scl can be connected to, connect +them using pinctrl, claim these resources and return it. It will return +`-ENODEV` if no such device can be found. The board DTS must enable these +devices with `status = "okay";`, mark them as `zephyr,deferred-init;` and +provide default pinctrl settings that will be overridden. + +## Status + +Currently, runtime routing is implemented for nRF SoCs, whose pin control +encoding can be computed at runtime and whose peripherals can be routed to +(almost) any pin via PSEL. On other SoCs the module compiles but the allocate +functions always return `-ENOSYS`. + +Analog pads are allocated through two function pairs, ADC inputs via +`iobroker_adc_allocate()` and DAC outputs via `iobroker_dac_allocate()`. Both +have no routing: the pad's analog function is fixed by the SoC, so the call +resolves it (for example a SAADC AIN number, or the RA's PmnPFS.ASEL analog +switch) from a per-SoC table, claims the pad and hands out a free channel slot +on the analog device. Zephyr's ADC API has no channel release, so the release +implementation unconfigures the slot per SoC (through `nrfx` for the SAADC). +See `src/nordic/nrf/iobroker_analog.c`, `src/nxp/mcxn/iobroker_analog.c`, +`src/renesas/ra/iobroker_analog.c` and `src/emul/iobroker_analog.c`. + +## Source layout + +The source is organized by vendor and SoC family under `src/`: + +``` +src/ + iobroker.c # SoC-agnostic core: package pin map + # lookup, GPIO controller split, claim + # registry, -ENOSYS stubs when routing + # is unavailable + iobroker_internal.h # helpers shared between core and + # vendor implementations (private) + nordic/ + nrf/ # every nRF SoC shares one pinctrl + iobroker_route.c # encoding, so the family is one dir + nxp/ + mcxn/ # NXP MCX N: LPADC inputs and the LPDAC + iobroker_analog.c # output pad resolve from the pin + # functions table +``` + +The core compiles on every SoC. Each vendor adds a `src///` +directory with an implementation of the bus allocate/release functions and a +corresponding `zephyr_library_sources_ifdef()` line in `CMakeLists.txt`; +without one the core's `#if !IOBROKER_ROUTING` stubs report `-ENOSYS`. When +an implementation covers a whole SoC family (as the nRF one does, keyed on +`CONFIG_PINCTRL_NRF`), the directory is named for the family. + +## Enabling + +The module is registered by the CircuitPython Zephyr application +(`ports/zephyr-cp/CMakeLists.txt`) via `ZEPHYR_EXTRA_MODULES`, so it works +without west manifest changes. Its core is always compiled and its callers +include `` directly; runtime routing additionally +requires `CONFIG_PINCTRL_DYNAMIC` and `CONFIG_DEVICE_DEINIT_SUPPORT`, which +the port enables by default on nRF SoCs. + +## Board tables + +The core does not know which instances are allocatable; the application must +provide per-board tables (the generated `board.c` always emits them): + +```c +const iobroker_instance_t iobroker_i2c_buses[]; // + _states[] and _bus_count +const iobroker_instance_t iobroker_spi_buses[]; // ... +const iobroker_instance_t iobroker_uart_buses[]; // ... +const struct device * const iobroker_gpio_port_devices[]; // + _indexes[] and _count +const iobroker_package_pin_t iobroker_package_pins[]; // + _pin_count +const uint16_t iobroker_reserved_pads[]; // + _pin_count +``` + +The package pin map is selected from the module's reference maps: +`Kconfig.packages` offers one option per transcribed package +(`packages/*.toml`), each visible only for the SoCs it applies to and +preselected for the development kits. The map's SoC pads are numbered by +the package's own pin ids (row-major ball order, or the datasheet's pin +number for a QFN/QFP package); each pad carries the global GPIO number +it bonds to when it sits on a GPIO controller. Analog-only pads (pads no +GPIO controller covers, e.g. MCX N's ANA pads) carry the datasheet's pad +name in `pad_name` and become pin objects named by `zephyr2cp.py` from +that name, so analogio can reach them. SoCs with no reference map fall +back to `IOBROKER_PACKAGE_ONE_TO_ONE`, an identity map where the package +pin number is the global pin number (gpio port index * 32 + pin within +the port), so boards without a transcribed physical package can still +resolve their pins. `IOBROKER_PACKAGE_NONE` is also available and +generates an empty map, making package pin lookups fail with `-EINVAL`. +The selected TOML (or the empty map) is rendered into a build-directory +translation unit at build time. The identity map needs no rendered +table: the core applies it directly. +New maps are transcribed from a SoC datasheet with `tools/gen_package.py` +(see the script's docstring; the datasheets live in `datasheets/`). +Each instance entry contains the Zephyr device, its `struct +pinctrl_dev_config` (via `PINCTRL_DT_DEV_CONFIG_DECLARE`/`_GET`) and, when the +devicetree state has fixed pins, the raw `pinctrl_soc_pin_t` values of the +"default" state. Instances whose devicetree "default" state leaves every +signal disconnected (`NRF_PIN_DISCONNECTED`) set `.dt_psels = NULL` and can be +routed to any pin at runtime. Instances with fixed devicetree pins are only +allocatable when a request matches their existing state. + +In the CircuitPython tree these tables are generated into the board's +`board.c` by `cptools/zephyr2cp.py` at build time. + +`iobroker_reserved_pads` lists the SoC pads that fixed peripherals (console +UART, flash instance, I2S, ...) drive at boot through their devicetree +pinctrl default state. `iobroker_pin_in_use()` reports them as busy so that +allocate() rejects requests for those pads with `-EBUSY` instead of +re-routing pads something else is already driving. The table only contains +connected pins; dynamically routable instances (all signals disconnected in +the devicetree) contribute nothing. + +The GPIO controller table pairs each controller device with its hardware +port index. The indexes define the global pin numbering (index * 32 + pin +within the port) shared with the pin objects, and let the module resolve a +global pin number back to the controller device and pin number that +Zephyr's GPIO API takes. + +## Public API + +See `include/iobroker/iobroker.h`. Signals are requested with +`package_pin_t` values (`PACKAGE_PIN(n)`): the physical package pin numbered +as the SoC datasheet numbers it (QFN pins 1..N; CSP/BGA balls numbered +sequentially in row-major order, with the datasheet's ball label noted in the +map and the generated C). The module resolves package pins to SoC pads +through the package pin map, `iobroker_package_pins[]`, selected via +`CONFIG_IOBROKER_PACKAGE_*` (see above); internally the lookup then +proceeds package pin -> SoC pad -> peripheral routing. `IOBROKER_NO_PIN` leaves a +signal disconnected. Pull resistors are chosen by the module per bus signal +(I2C SDA/SCL and UART RX idle high). Every allocate call must be paired with +`iobroker_release()`, so the application owns the lifecycle. Releasing also +leaves the pins quiescent: `iobroker_release()` de-initializes the device, +which applies its low-power pinctrl state to the routed pins. +`iobroker_pin_in_use()` reports whether a package pin is claimed by a +currently allocated instance, and `allocate()` refuses (-EBUSY) requests that +would double-use a pin. Package pins can also be claimed for plain GPIO use +with `iobroker_gpio_allocate()` / `iobroker_gpio_release()`: the caller +configures the pad while the claim is held and the module returns it to a +quiescent state (disconnected) on release, and GPIO claims conflict with bus +allocations the same way bus allocations conflict with each other. +`iobroker_gpio_allocate()` resolves the package pin through the map +and returns both the GPIO controller device and the pin number within it. +`iobroker_gpio_package_pin()` maps a GPIO controller's hardware port index +and pin number (the two halves of the global pin numbering) back to the +package pin the pad is bonded to. + +## Externalizing + +This module is application-agnostic: it depends only on Zephyr. To move it +into its own repository: + +1. Copy this directory (minus this README) to the new repo root, keeping + `zephyr/module.yml`, `CMakeLists.txt`, `Kconfig`, `include/` and `src/`. +2. Point `ZEPHYR_EXTRA_MODULES` in `ports/zephyr-cp/CMakeLists.txt` at the new + checkout, or register it as a project in `zephyr-config/west.yml`. +3. Keep `iobroker.h`'s struct/function names stable — the generated + board tables are part of the module's de-facto API. diff --git a/ports/zephyr-cp/internal-modules/iobroker/datasheets/README.md b/ports/zephyr-cp/internal-modules/iobroker/datasheets/README.md new file mode 100644 index 00000000000..584d82e7971 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/datasheets/README.md @@ -0,0 +1,64 @@ +# Datasheets + +The package pin maps in `../packages/` are transcribed from Nordic's SoC +datasheets. The PDFs are not redistributed in this repository; download them +from Nordic (https://docs.nordicsemi.com) into this directory and verify them +against the SHA-256 hashes below before regenerating a map. + +| File | SHA-256 | +|------|---------| +| `nRF54L15_nRF54L10_nRF54L05_Datasheet_v1.0.pdf` | `ade0d340ba95e31f8299e6721b08d53a702962335d9b87fa202f8d2767603554` | +| `nRF54LM20A_nRF54LM20B_Datasheet_v1.0.pdf` | `03a834fb52be8cf6248df6f67737f055c035cfb83492d44d58bdc2f0bfa3a3af` | +| `nRF5340_PS_v1.6.pdf` | `fe5c7e8908c94080548a1ddd4ec8b96aa09a4b97239fe9b32afb49eb8cf2fc87` | +| `nRF52840_PS_v1.1.pdf` | `c619e336b9c0610663273041f057f2537a65fd408ce0c5b8214a26de2aa88422` | +| `[nRF52840] MDBT50Q-1MV2 & MDBT50Q-P1MV2_Ver.L spec.pdf` | `61fec8c0c9f8c33175be2237a8ebba73c6cfc0a3572fe3835fd341079c103d03` | +| `rp2040-datasheet.pdf` | `be56fbb75ba0ae9e26558a73c93ac3e75c2ad4e6878d3b6703de2a76d886ea8c` | +| `MCXNP184M150F70.pdf` | `f3a680d2ac36960f81b474e9e7589ab7a54fc2690eaf9baa67de1d442c5135aa` | +| `MCXNP184M150F70_Pinout.xlsx` | `ba047de2d40dc99caf32502abd3afb69e88f8168dd767949f57d37a67e4295a5` | +| `UM12018(3).pdf` | `2036bf71b7a474e7cd37c55e90af2d87d09bd41d2decf30630480f7de1c68469` | +| `FRDM-MCXN947-TOP.avif` | `5ac1064c118947bd1418e248d1b6210d739fda7c8af9d7b4e600910368173e46` | + +The nRF52840 Product Specification is Nordic's, downloadable from +https://docs.nordicsemi.com; the copy here came from +https://cdn-learn.adafruit.com/assets/assets/000/092/427/original/nRF52840_PS_v1.1.pdf. +The MDBT50Q module datasheet is Raytac's, from +https://www.raytac.com. The RP2040 datasheet is Raspberry Pi's, from +https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf. + +MCXNP184M150F70.pdf is NXP's MCXN947/946/547/546/537/536/527/526/247 +product data sheet (document MCXNP184M150F70, Rev. 8.2, 11 June 2026), +downloadable from https://www.nxp.com (MCX-N947 product page). +UM12018(3).pdf is NXP's FRDM-MCXN947 Board User Manual (UM12018 Rev. 2.0), +from https://www.nxp.com/docs/en/user-guide/UM12018.pdf. The FRDM board +photos come from the board's website (https://www.nxp.com/design/design-center/development-boards-and-designs/FRDM-MCXN947) +and show the header silkscreen. + +MCXNP184M150F70_Pinout.xlsx is the pinout workbook attached to the data +sheet (Signal multiplexing and pin assignments; from the document's +Download PDF bundle at https://docs.nxp.com/bundle/MCXNP184M150F70), +with the full ball/pin map of each package, power pins included. + +The MCX N maps in `packages/mcxn947_*.toml` come from the datasheet's +section 6.1 Table 93 (Pinmux): the 184BGA column is the VFBGA184 package +(Zephyr's MCXN947VDF part number, the FRDM board's chip) and the 100HLQFP +N94 column is the MCXN947/MCXN946 100-pin HLQFP (MCXN947VNL). The +transcription command lines are recorded in the header of each +`packages/*.toml`; the pin ids for the VFBGA184 map come from the +workbook's VFBGA184 tab (the full 184-ball map, power balls included, so +the pad ids carry the datasheet's gaps). Table 93 also lists the +analog-only pads (Pad type - ANA), which are not wired to a GPIO +controller: ANA_0 = ADC0_A0 (the FRDM's Arduino A0), ANA_1 = ADC0_B0 +(Arduino A1), ANA_4 = ADC1_A0 (the mikroBUS AN input), ANA_5 = ADC1_B0, +ANA_6 = the DAC2 (HPDAC) output, ANA_14/18/22 = OPAMP inputs, ANA_7 +unused. Both mcxn947 maps carry them +in the row-major ball numbering; zephyr2cp names their pin objects +ANA_x and binds the board's A0/A1 aliases to them, so analogio reaches +them. The DAC2 output stays unsupported: the iobroker DAC backend binds +the LPDAC (dac0, muxed on the P4_02 pad). + +The transcription command lines are recorded in the header of each +`packages/*.toml` (section number, package name, pin count). After running +`pdftotext -layout` on a verified PDF, the same command reproduces the map. + +This directory is ignored by git; this README is force-added so the hashes +stay with the code. diff --git a/ports/zephyr-cp/internal-modules/iobroker/include/iobroker/iobroker.h b/ports/zephyr-cp/internal-modules/iobroker/include/iobroker/iobroker.h new file mode 100644 index 00000000000..5fc87bd8634 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/include/iobroker/iobroker.h @@ -0,0 +1,284 @@ +// iobroker: dynamic peripheral allocation and runtime pin routing. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +// Allocate a free bus instance (I2C, SPI, UART) enabled in the devicetree, +// route it to the requested pins at runtime and hand the Zephyr device to the +// caller. Per-board tables of allocatable instances are provided by the +// application (the CircuitPython build generates them from the board's +// devicetree; see modules/iobroker/README.md for the contract). +// +// Runtime routing works on nRF SoCs because their pin control encoding is a +// simple bit-packed value (see zephyr/dt-bindings/pinctrl/nrf-pinctrl.h and +// soc/nordic/common/pinctrl_soc.h) that can be computed at runtime, and any +// peripheral function can be routed to (almost) any pin via PSEL. + +#pragma once + +#include +#include +#include + +#include +#include + +// Runtime bus routing lets the module re-route a bus instance's pins at +// runtime. It is available when Zephyr is built with dynamic pinctrl states and +// device de-init support. Without it the bus allocate/release functions report +// -ENOSYS and the bus instance tables are not used. +#if defined(CONFIG_PINCTRL_NRF) && defined(CONFIG_PINCTRL_DYNAMIC) && \ + defined(CONFIG_DEVICE_DEINIT_SUPPORT) +#define IOBROKER_ROUTING 1 +#else +#define IOBROKER_ROUTING 0 +#endif + +// ADC pad allocation is available when a supported ADC device is built: the +// nRF SAADC driver (src/nordic/nrf/), the NXP MCX N LPADC +// (src/nxp/mcxn/) or the emulated ADC (src/emul/, native_sim). Without it +// the allocate function reports -ENOSYS. +#if defined(CONFIG_ADC_NRFX_SAADC) || defined(CONFIG_ADC_EMUL) || \ + defined(CONFIG_ADC_MCUX_LPADC) +#define IOBROKER_ADC 1 +#else +#define IOBROKER_ADC 0 +#endif + +// DAC pad allocation is available when a supported DAC device is built: the +// Renesas RA DAC (src/renesas/ra/), the NXP MCX N LPDAC (src/nxp/mcxn/) or +// the emulated test DAC (src/emul/, native_sim). Without it the allocate +// function reports -ENOSYS. +#if defined(CONFIG_DAC_TEST) || defined(CONFIG_DAC_RENESAS_RA) || \ + defined(CONFIG_DAC_MCUX_LPDAC) +#define IOBROKER_DAC 1 +#else +#define IOBROKER_DAC 0 +#endif + +// Signals needed by the widest bus (UART with tx/rx/rts/cts). +#define IOBROKER_MAX_PINS 4 + +// Package pin that leaves a signal disconnected (only valid for optional +// signals such as UART rts/cts). +#define IOBROKER_NO_PIN ((package_pin_t)0xffff) + +// Marker for a SoC pad with no GPIO controller (an analog-only pad). +#define IOBROKER_PAD_NO_GPIO ((uint16_t)0xffff) + +// A physical pin of the SoC's package, numbered the way the SoC datasheet +// numbers its package pins (1..N for QFN-style packages; BGA-style packages +// use the datasheet's sequential numbering of the balls). Translating a pin +// name from a schematic or board layout to a package pin is a board concern; +// the module only knows the package pin -> SoC pad mapping below. +typedef uint16_t package_pin_t; + +// Wrap a datasheet package pin number into a package_pin_t. +#define PACKAGE_PIN(number) ((package_pin_t)(number)) + +// One entry of the package pin map: a physical package pin and the SoC pad +// it is bonded to. The SoC pad is numbered by the package's pin ids -- balls +// in row-major order for a ball grid (the datasheet only labels them, e.g. +// A17), or the datasheet's pin number for a QFN/QFP package. Pads bonded to +// a GPIO controller carry that bond as their global GPIO number (port index +// * 32 + pin within the port, what the GPIO controller table below +// resolves); analog-only pads, which no GPIO controller covers, have no +// bond and only the analog APIs reach them, through their package pin. +// Package pins that bond neither a GPIO nor an analog function (power, ...) +// are omitted; requesting one fails with -EINVAL. +// +// The map is generated by the module build from the selected IOBROKER_PACKAGE +// choice (see packages/), or as an empty map when no package applies to the +// SoC. With an empty map every package pin lookup fails with -EINVAL. +typedef struct { + package_pin_t package_pin; + uint16_t soc_pad; + // Global GPIO number the pad is bonded to, or IOBROKER_PAD_NO_GPIO when + // the pad has no GPIO controller (an analog-only pad). + uint16_t gpio_pad; +} iobroker_package_pin_t; + +extern const iobroker_package_pin_t iobroker_package_pins[]; +extern const size_t iobroker_package_pin_count; + +// GPIO controller table: the board lists every GPIO controller together with +// its hardware port index, which defines the global pin numbering +// (index * 32 + pin). Generated into board.c; boards without GPIO +// controllers generate an empty table, so lookups return -EINVAL. +extern const struct device *const iobroker_gpio_port_devices[]; +extern const uint8_t iobroker_gpio_port_indexes[]; +extern const size_t iobroker_gpio_port_count; + +// Resolve a global pin number to its GPIO controller device and the pin +// number within that controller (the two values Zephyr's GPIO API takes). +// Returns 0, or -EINVAL when no controller covers the number. +int iobroker_gpio_split(uint16_t number, const struct device **port_out, + gpio_pin_t *pin_out); + +// Map a GPIO controller's hardware port index and the pin number within it +// (the two halves of the global pin numbering: index * 32 + pin) to the +// package pin the pad is bonded to. The inverse of the package pin map's +// soc_pad column. Returns 0, or -EINVAL when the pad has no entry in the +// package pin map. +int iobroker_gpio_package_pin(uint8_t port, gpio_pin_t pin, + package_pin_t *package_pin_out); + +#if defined(CONFIG_PINCTRL_NRF) + +#include + +// Description of one allocatable bus instance. Filled in by the board tables. +typedef struct { + const struct device *dev; + // Pin control configuration of the device. Mutable because + // CONFIG_PINCTRL_DYNAMIC moves these to RAM so that states can be + // swapped at runtime. + struct pinctrl_dev_config *pcfg; + // Pin control entries of the devicetree "default" state, or NULL when + // the instance was enabled with disconnected pins so that it can be + // routed dynamically to any pin. + const pinctrl_soc_pin_t *dt_psels; + uint8_t dt_psel_count; +} iobroker_instance_t; + +// Runtime bookkeeping for one allocatable bus instance. The arrays live for +// the life of the firmware because Zephyr's pinctrl API keeps pointers to +// them inside the device's pinctrl_dev_config. +typedef struct { + pinctrl_soc_pin_t default_pins[IOBROKER_MAX_PINS]; + pinctrl_soc_pin_t sleep_pins[IOBROKER_MAX_PINS]; + struct pinctrl_state states[2]; + bool in_use; + // True when the instance's states were swapped for runtime-built entries. + bool routed; + // Package pins the current allocation claims (copies of the request). + // Used by iobroker_pin_in_use(); empty while the instance is free. + package_pin_t pins[IOBROKER_MAX_PINS]; + uint8_t pin_count; +} iobroker_state_t; + +// Generated board tables, emitted by the board's generated board.c whenever +// the nRF pinctrl driver is built. They are used only when runtime routing is +// available (IOBROKER_ROUTING); boards without it still define them. +extern const iobroker_instance_t iobroker_i2c_buses[]; +extern const size_t iobroker_i2c_bus_count; +extern iobroker_state_t iobroker_i2c_bus_states[]; + +extern const iobroker_instance_t iobroker_spi_buses[]; +extern const size_t iobroker_spi_bus_count; +extern iobroker_state_t iobroker_spi_bus_states[]; + +extern const iobroker_instance_t iobroker_uart_buses[]; +extern const size_t iobroker_uart_bus_count; +extern iobroker_state_t iobroker_uart_bus_states[]; + +// SoC pads owned by fixed peripherals (console UART, flash instance, I2S, +// ...): the pads their devicetree pinctrl default state drives at boot. +// iobroker_pin_in_use() reports these as always busy so that allocate() +// rejects requests for them with -EBUSY. +extern const uint16_t iobroker_reserved_pads[]; +extern const size_t iobroker_reserved_pads_count; + +#endif // CONFIG_PINCTRL_NRF + +// The functions below return 0 on success and set *dev_out to the Zephyr +// device of an allocated instance. A negative errno is returned on failure: +// -ENODEV: no compatible instance is free +// -ENOSYS: dynamic pin routing is unsupported on this SoC +// -EBUSY: a requested pin is already claimed by an allocated instance +// -EINVAL/-EIO: a pin or routing operation failed +// Optional signals may be disconnected (IOBROKER_NO_PIN). Every allocate +// call must be paired with iobroker_release(). +int iobroker_i2c_allocate(package_pin_t sda, package_pin_t scl, + const struct device **dev_out); +int iobroker_spi_allocate(package_pin_t clock, package_pin_t mosi, + package_pin_t miso, const struct device **dev_out); +int iobroker_uart_allocate(package_pin_t tx, package_pin_t rx, + package_pin_t rts, package_pin_t cts, const struct device **dev_out); + +// Allocate a package pin for the ADC. Analog inputs have no runtime routing: +// the pad's analog input is fixed by the SoC, so the call resolves and +// returns it in *input_out (the input selector the device's channel +// configuration takes, such as a SAADC AIN number; undefined when the device +// has no selectable inputs, as on the emulated ADC). The call also assigns a +// free channel slot on the ADC device, returned in *channel_out; the caller +// configures the channel itself (adc_channel_setup()) with channel_id = +// *channel_out and input_positive = *input_out where the device takes an +// input selector. The pad stays claimed while the allocation is held, so bus +// allocate() calls and GPIO allocations refuse it with -EBUSY. +// +// Returns 0, or a negative errno: +// -ENOSYS: no ADC support on this SoC +// -EINVAL: the pin is disconnected, unknown, or has no analog function +// -EBUSY: the pad is already claimed +// -ENOMEM: the analog claim registry is full +int iobroker_adc_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out); + +// Release an ADC pad allocated with iobroker_adc_allocate(), unconfigure its +// channel if the ADC device needs that (Zephyr's ADC API has no channel +// release, so implementations handle it per SoC) and return the pad to a +// quiescent state. Pass the device and channel the allocate call returned. +// Returns true when an allocation was held. +bool iobroker_adc_release(const struct device *dev, uint8_t channel); + +// Allocate a package pin for the DAC and hand out a free channel slot on the +// DAC device. DAC outputs are like ADC inputs fixed per pad: no runtime +// routing, the pad's analog switch is applied by the implementation. The +// caller configures the channel itself (dac_channel_setup()) with channel_id +// = *channel_out and input_positive = *input_out where the device takes an +// input selector. The pad stays claimed while the allocation is held, so bus +// allocate() calls and GPIO allocations refuse it with -EBUSY. +// +// Returns 0, or a negative errno: +// -ENOSYS: no DAC support on this SoC +// -EINVAL: the pin is disconnected, unknown, or has no analog function +// -EBUSY: the pad is already claimed +// -ENOMEM: the analog claim registry is full +int iobroker_dac_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out); + +// Release a DAC pad allocated with iobroker_dac_allocate(), unconfigure its +// channel if the DAC device needs that (Zephyr's DAC API has no channel +// release, so implementations handle it per SoC) and return the pad to a +// quiescent state. Pass the device and channel the allocate call returned. +// Returns true when an allocation was held. +bool iobroker_dac_release(const struct device *dev, uint8_t channel); + +// Returns true when the package pin is currently claimed by an allocated bus +// instance, a GPIO allocation, or a fixed peripheral (console UART, flash +// instance, ... whose pads are listed in iobroker_reserved_pads). Call this +// before allocate() to find out which requested pin is already in use. +// Disconnected signals (IOBROKER_NO_PIN) and package pins with no SoC pad are +// never in use. +bool iobroker_pin_in_use(package_pin_t pin); + +// Allocate a package pin for plain GPIO use. The module only claims it: while +// held, bus allocate() calls and further GPIO allocations fail with -EBUSY. +// The pin is resolved through the package pin map, and both values Zephyr's +// GPIO API needs are returned: *port_out receives the GPIO controller device +// and *pin_out the pin number within it, so that the caller can configure the +// pad itself (gpio_pin_configure()). Returns 0, or a negative errno: +// -EINVAL: pin is disconnected (IOBROKER_NO_PIN) or has no entry in the +// package pin map +// -EBUSY: the pin is already claimed +// -ENOMEM: the GPIO claim registry is full +int iobroker_gpio_allocate(package_pin_t pin, + const struct device **port_out, gpio_pin_t *pin_out); + +// Release a GPIO claim made with iobroker_gpio_allocate() and return the pad +// to a quiescent state: disconnected from any peripheral routing, input +// buffer and driver off, no pulls (a plain input on SoCs without +// GPIO_DISCONNECTED support). Pass the device and pin number that the +// allocate call returned. Returns true when a claim was held. +bool iobroker_gpio_release(const struct device *port, gpio_pin_t number); + +// Release an instance previously returned by one of the allocate functions. +// Returns true when the instance had been dynamically routed, in which case +// the caller is responsible for resetting the pins it had routed to it. The +// device is de-initialized on release, so it is left uninitialized; the +// caller initializes it again when it allocates the instance next. +bool iobroker_release(const struct device *dev); diff --git a/ports/zephyr-cp/internal-modules/iobroker/packages/mcxn947_vdf.toml b/ports/zephyr-cp/internal-modules/iobroker/packages/mcxn947_vdf.toml new file mode 100644 index 00000000000..58757be8353 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/packages/mcxn947_vdf.toml @@ -0,0 +1,954 @@ +# Package pin map for mcxn947_vdf, transcribed from MCXNP184M150F70.pdf, +# section 6.1 Table 93 (Pinmux), 184BGA column ("ALL": the same ball +# assignments apply across the family), with a one-off pdftotext -layout +# parse of the datasheet and the workbook's ball map. Review against the NXP package drawing (98ASA01888D) before +# editing by hand; regenerate instead when the datasheet changes. +# +# Ball packages: 'pin' numbers the 184 present balls sequentially in +# row-major order (the datasheet only labels them, e.g. A17), so power +# balls leave gaps; the pad->pin ids come from the package's Pinout +# workbook (MCXNP184M150F70_Pinout.xlsx, VFBGA184 tab). +# + +# The ``gpio`` column is the pad's global GPIO number (gpio port index * 32 +# + pin, the numbering the GPIO controller table resolves) when the pad +# bonds a GPIO controller; pads without it (the analog-only pads) are +# reachable only through the analog APIs. + +# The analog-only pads (Pad type - ANA, no GPIO controller) carry the +# datasheet's ANA_x pad names and integrate into the row-major ball +# numbering; zephyr2cp turns them into pin objects named by their +# pad_name: +# ANA_0 = ADC0_A0 (ball P3, pad 139) +# ANA_1 = ADC0_B0 (ball R3, pad 150) +# ANA_14 = OPAMP0_INN (ball U6, pad 178) +# ANA_18 = OPAMP1_INN (ball U8, pad 179) +# ANA_22 = OPAMP2_INN (ball U10, pad 180) +# ANA_4 = ADC1_A0 (ball T2, pad 162) +# ANA_5 = ADC1_B0 (ball T3, pad 163) +# ANA_6 = DAC2_OUT (ball U2, pad 176) +# ANA_7 = unused (ball U4, pad 177) + +name = "mcxn947_vdf" +socs = ["SOC_MCXN947"] + +[[pins]] +pin = 1 +ball = "A1" +pad_name = "P1.08" +gpio = 40 + + +[[pins]] +pin = 2 +ball = "A2" +pad_name = "P1.07" +gpio = 39 + + +[[pins]] +pin = 3 +ball = "A4" +pad_name = "P1.04" +gpio = 36 + + +[[pins]] +pin = 4 +ball = "A6" +pad_name = "P0.25" +gpio = 25 + + +[[pins]] +pin = 5 +ball = "A8" +pad_name = "P0.21" +gpio = 21 + + +[[pins]] +pin = 6 +ball = "A10" +pad_name = "P0.17" +gpio = 17 + + +[[pins]] +pin = 7 +ball = "A12" +pad_name = "P0.09" +gpio = 9 + + +[[pins]] +pin = 8 +ball = "A14" +pad_name = "P0.05" +gpio = 5 + + +[[pins]] +pin = 9 +ball = "A16" +pad_name = "P0.01" +gpio = 1 + + +[[pins]] +pin = 10 +ball = "A17" +pad_name = "P0.00" +gpio = 0 + + +[[pins]] +pin = 11 +ball = "B1" +pad_name = "P1.09" +gpio = 41 + + +[[pins]] +pin = 12 +ball = "B2" +pad_name = "P1.06" +gpio = 38 + + +[[pins]] +pin = 13 +ball = "B3" +pad_name = "P1.05" +gpio = 37 + + +[[pins]] +pin = 14 +ball = "B4" +pad_name = "P1.03" +gpio = 35 + + +[[pins]] +pin = 15 +ball = "B6" +pad_name = "P0.24" +gpio = 24 + + +[[pins]] +pin = 16 +ball = "B7" +pad_name = "P0.23" +gpio = 23 + + +[[pins]] +pin = 17 +ball = "B8" +pad_name = "P0.22" +gpio = 22 + + +[[pins]] +pin = 18 +ball = "B10" +pad_name = "P0.16" +gpio = 16 + + +[[pins]] +pin = 19 +ball = "B11" +pad_name = "P0.11" +gpio = 11 + + +[[pins]] +pin = 20 +ball = "B12" +pad_name = "P0.10" +gpio = 10 + + +[[pins]] +pin = 21 +ball = "B14" +pad_name = "P0.04" +gpio = 4 + + +[[pins]] +pin = 22 +ball = "B15" +pad_name = "P0.03" +gpio = 3 + + +[[pins]] +pin = 23 +ball = "B16" +pad_name = "P0.02" +gpio = 2 + + +[[pins]] +pin = 24 +ball = "B17" +pad_name = "P3.00" +gpio = 96 + + +[[pins]] +pin = 25 +ball = "C3" +pad_name = "P1.10" +gpio = 42 + + +[[pins]] +pin = 26 +ball = "C4" +pad_name = "P1.02" +gpio = 34 + + +[[pins]] +pin = 27 +ball = "C5" +pad_name = "P1.01" +gpio = 33 + + +[[pins]] +pin = 28 +ball = "C6" +pad_name = "P1.00" +gpio = 32 + + +[[pins]] +pin = 29 +ball = "C8" +pad_name = "P0.20" +gpio = 20 + + +[[pins]] +pin = 30 +ball = "C9" +pad_name = "P0.19" +gpio = 19 + + +[[pins]] +pin = 31 +ball = "C10" +pad_name = "P0.18" +gpio = 18 + + +[[pins]] +pin = 32 +ball = "C12" +pad_name = "P0.08" +gpio = 8 + + +[[pins]] +pin = 33 +ball = "C13" +pad_name = "P0.07" +gpio = 7 + + +[[pins]] +pin = 34 +ball = "C14" +pad_name = "P0.06" +gpio = 6 + + +[[pins]] +pin = 35 +ball = "C15" +pad_name = "P3.01" +gpio = 97 + + +[[pins]] +pin = 36 +ball = "D1" +pad_name = "P1.13" +gpio = 45 + + +[[pins]] +pin = 37 +ball = "D2" +pad_name = "P1.12" +gpio = 44 + + +[[pins]] +pin = 38 +ball = "D3" +pad_name = "P1.11" +gpio = 43 + + +[[pins]] +pin = 39 +ball = "D4" +pad_name = "P1.14" +gpio = 46 + + +[[pins]] +pin = 41 +ball = "D7" +pad_name = "P0.31" +gpio = 31 + + +[[pins]] +pin = 43 +ball = "D11" +pad_name = "P0.12" +gpio = 12 + + +[[pins]] +pin = 45 +ball = "D14" +pad_name = "P3.07" +gpio = 103 + + +[[pins]] +pin = 46 +ball = "D15" +pad_name = "P3.02" +gpio = 98 + + +[[pins]] +pin = 47 +ball = "D16" +pad_name = "P3.03" +gpio = 99 + + +[[pins]] +pin = 48 +ball = "D17" +pad_name = "P3.06" +gpio = 102 + + +[[pins]] +pin = 49 +ball = "E4" +pad_name = "P1.15" +gpio = 47 + + +[[pins]] +pin = 51 +ball = "E7" +pad_name = "P0.30" +gpio = 30 + + +[[pins]] +pin = 52 +ball = "E8" +pad_name = "P0.28" +gpio = 28 + + +[[pins]] +pin = 53 +ball = "E10" +pad_name = "P0.27" +gpio = 27 + + +[[pins]] +pin = 54 +ball = "E11" +pad_name = "P0.14" +gpio = 14 + + +[[pins]] +pin = 56 +ball = "E14" +pad_name = "P3.08" +gpio = 104 + + +[[pins]] +pin = 57 +ball = "F1" +pad_name = "P1.30" +gpio = 62 + + +[[pins]] +pin = 58 +ball = "F2" +pad_name = "P1.31" +gpio = 63 + + +[[pins]] +pin = 60 +ball = "F4" +pad_name = "P1.17" +gpio = 49 + + +[[pins]] +pin = 61 +ball = "F6" +pad_name = "P1.16" +gpio = 48 + + +[[pins]] +pin = 62 +ball = "F8" +pad_name = "P0.29" +gpio = 29 + + +[[pins]] +pin = 63 +ball = "F10" +pad_name = "P0.26" +gpio = 26 + + +[[pins]] +pin = 64 +ball = "F12" +pad_name = "P0.13" +gpio = 13 + + +[[pins]] +pin = 65 +ball = "F14" +pad_name = "P3.04" +gpio = 100 + + +[[pins]] +pin = 66 +ball = "F15" +pad_name = "P3.09" +gpio = 105 + + +[[pins]] +pin = 67 +ball = "F16" +pad_name = "P3.11" +gpio = 107 + + +[[pins]] +pin = 68 +ball = "F17" +pad_name = "P3.10" +gpio = 106 + + +[[pins]] +pin = 70 +ball = "G4" +pad_name = "P1.18" +gpio = 50 + + +[[pins]] +pin = 71 +ball = "G5" +pad_name = "P1.19" +gpio = 51 + + +[[pins]] +pin = 74 +ball = "G13" +pad_name = "P0.15" +gpio = 15 + + +[[pins]] +pin = 75 +ball = "G14" +pad_name = "P3.05" +gpio = 101 + + +[[pins]] +pin = 76 +ball = "G16" +pad_name = "P3.12" +gpio = 108 + + +[[pins]] +pin = 77 +ball = "H1" +pad_name = "P2.01" +gpio = 65 + + +[[pins]] +pin = 78 +ball = "H2" +pad_name = "P2.00" +gpio = 64 + + +[[pins]] +pin = 79 +ball = "H3" +pad_name = "P2.02" +gpio = 66 + + +[[pins]] +pin = 87 +ball = "H15" +pad_name = "P3.15" +gpio = 111 + + +[[pins]] +pin = 88 +ball = "H16" +pad_name = "P3.13" +gpio = 109 + + +[[pins]] +pin = 89 +ball = "H17" +pad_name = "P3.14" +gpio = 110 + + +[[pins]] +pin = 90 +ball = "J3" +pad_name = "P2.03" +gpio = 67 + + +[[pins]] +pin = 95 +ball = "J15" +pad_name = "P3.16" +gpio = 112 + + +[[pins]] +pin = 96 +ball = "K1" +pad_name = "P2.05" +gpio = 69 + + +[[pins]] +pin = 97 +ball = "K2" +pad_name = "P2.06" +gpio = 70 + + +[[pins]] +pin = 98 +ball = "K3" +pad_name = "P2.04" +gpio = 68 + + +[[pins]] +pin = 99 +ball = "K5" +pad_name = "P1.20" +gpio = 52 + + +[[pins]] +pin = 104 +ball = "K12" +pad_name = "P5.05" +gpio = 165 + + +[[pins]] +pin = 105 +ball = "K13" +pad_name = "P5.06" +gpio = 166 + + +[[pins]] +pin = 106 +ball = "K15" +pad_name = "P3.17" +gpio = 113 + + +[[pins]] +pin = 107 +ball = "K16" +pad_name = "P3.18" +gpio = 114 + + +[[pins]] +pin = 108 +ball = "K17" +pad_name = "P3.19" +gpio = 115 + + +[[pins]] +pin = 109 +ball = "L2" +pad_name = "P2.07" +gpio = 71 + + +[[pins]] +pin = 110 +ball = "L4" +pad_name = "P1.22" +gpio = 54 + + +[[pins]] +pin = 111 +ball = "L5" +pad_name = "P1.21" +gpio = 53 + + +[[pins]] +pin = 114 +ball = "L13" +pad_name = "P5.07" +gpio = 167 + + +[[pins]] +pin = 115 +ball = "L14" +pad_name = "P5.08" +gpio = 168 + + +[[pins]] +pin = 116 +ball = "L16" +pad_name = "P3.21" +gpio = 117 + + +[[pins]] +pin = 117 +ball = "M1" +pad_name = "P2.09" +gpio = 73 + + +[[pins]] +pin = 118 +ball = "M2" +pad_name = "P2.08" +gpio = 72 + + +[[pins]] +pin = 119 +ball = "M3" +pad_name = "P2.10" +gpio = 74 + + +[[pins]] +pin = 120 +ball = "M4" +pad_name = "P1.23" +gpio = 55 + + +[[pins]] +pin = 121 +ball = "M6" +pad_name = "P4.04" +gpio = 132 + + +[[pins]] +pin = 122 +ball = "M8" +pad_name = "P4.05" +gpio = 133 + + +[[pins]] +pin = 123 +ball = "M10" +pad_name = "P5.02" +gpio = 162 + + +[[pins]] +pin = 124 +ball = "M12" +pad_name = "P5.04" +gpio = 164 + + +[[pins]] +pin = 125 +ball = "M14" +pad_name = "P5.09" +gpio = 169 + + +[[pins]] +pin = 126 +ball = "M15" +pad_name = "P3.23" +gpio = 119 + + +[[pins]] +pin = 127 +ball = "M16" +pad_name = "P3.22" +gpio = 118 + + +[[pins]] +pin = 128 +ball = "M17" +pad_name = "P3.20" +gpio = 116 + + +[[pins]] +pin = 129 +ball = "N4" +pad_name = "P2.11" +gpio = 75 + + +[[pins]] +pin = 131 +ball = "N7" +pad_name = "P4.06" +gpio = 134 + + +[[pins]] +pin = 132 +ball = "N8" +pad_name = "P4.14" +gpio = 142 + + +[[pins]] +pin = 133 +ball = "N10" +pad_name = "P4.18" +gpio = 146 + + +[[pins]] +pin = 134 +ball = "N11" +pad_name = "P5.03" +gpio = 163 + + +[[pins]] +pin = 137 +ball = "P1" +pad_name = "P4.00" +gpio = 128 + + +[[pins]] +pin = 138 +ball = "P2" +pad_name = "P4.01" +gpio = 129 + + +[[pins]] +pin = 139 +ball = "P3" +pad_name = "ANA_0" + + +[[pins]] +pin = 150 +ball = "R3" +pad_name = "ANA_1" + + +[[pins]] +pin = 154 +ball = "R8" +pad_name = "P4.16" +gpio = 144 + + +[[pins]] +pin = 155 +ball = "R9" +pad_name = "P4.17" +gpio = 145 + + +[[pins]] +pin = 156 +ball = "R10" +pad_name = "P4.19" +gpio = 147 + + +[[pins]] +pin = 161 +ball = "T1" +pad_name = "P4.02" +gpio = 130 + + +[[pins]] +pin = 162 +ball = "T2" +pad_name = "ANA_4" + + +[[pins]] +pin = 163 +ball = "T3" +pad_name = "ANA_5" + + +[[pins]] +pin = 164 +ball = "T4" +pad_name = "P4.07" +gpio = 135 + + +[[pins]] +pin = 165 +ball = "T6" +pad_name = "P4.12" +gpio = 140 + + +[[pins]] +pin = 166 +ball = "T7" +pad_name = "P4.13" +gpio = 141 + + +[[pins]] +pin = 167 +ball = "T8" +pad_name = "P4.15" +gpio = 143 + + +[[pins]] +pin = 168 +ball = "T10" +pad_name = "P4.20" +gpio = 148 + + +[[pins]] +pin = 169 +ball = "T11" +pad_name = "P4.21" +gpio = 149 + + +[[pins]] +pin = 170 +ball = "T12" +pad_name = "P4.22" +gpio = 150 + + +[[pins]] +pin = 175 +ball = "U1" +pad_name = "P4.03" +gpio = 131 + + +[[pins]] +pin = 176 +ball = "U2" +pad_name = "ANA_6" + + +[[pins]] +pin = 177 +ball = "U4" +pad_name = "ANA_7" + + +[[pins]] +pin = 178 +ball = "U6" +pad_name = "ANA_14" + + +[[pins]] +pin = 179 +ball = "U8" +pad_name = "ANA_18" + + +[[pins]] +pin = 180 +ball = "U10" +pad_name = "ANA_22" + + +[[pins]] +pin = 181 +ball = "U12" +pad_name = "P4.23" +gpio = 151 + + +[[pins]] +pin = 183 +ball = "U16" +pad_name = "P5.00" +gpio = 160 + + +[[pins]] +pin = 184 +ball = "U17" +pad_name = "P5.01" +gpio = 161 diff --git a/ports/zephyr-cp/internal-modules/iobroker/packages/mcxn947_vnl.toml b/ports/zephyr-cp/internal-modules/iobroker/packages/mcxn947_vnl.toml new file mode 100644 index 00000000000..e987d11372d --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/packages/mcxn947_vnl.toml @@ -0,0 +1,529 @@ +# Package pin map for mcxn947_vnl, transcribed from MCXNP184M150F70.pdf, +# section 6.1 Table 93 (Pinmux), 100HLQFP N94 column, with a one-off +# pdftotext -layout parse. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. +# +# QFN-style package: 'pin' holds the datasheet's pin number (1..100); the +# pads the package does not bond are omitted, so the numbering has gaps. + +# The ``gpio`` column is the pad's global GPIO number (gpio port index * 32 +# + pin, the numbering the GPIO controller table resolves) when the pad +# bonds a GPIO controller; pads without it (the analog-only pads) are +# reachable only through the analog APIs. + +# The analog-only pads (ANA) carry datasheet pad names: +# ANA_0 = ADC0_A0 (pin 22, pad 139) +# ANA_1 = ADC0_B0 (pin 23, pad 150) +# ANA_14 = OPAMP0_INN (pin 36, pad 178) +# ANA_18 = OPAMP1_INN (pin 39, pad 179) +# ANA_4 = ADC1_A0 (pin 26, pad 162) +# ANA_5 = ADC1_B0 (pin 27, pad 163) +# ANA_6 = DAC2_OUT (pin 28, pad 176) +# ANA_7 = unused (pin 29, pad 177) + +[[pins]] +pin = 76 +pad_name = "P0.00" +gpio = 0 + + +[[pins]] +pin = 77 +pad_name = "P0.01" +gpio = 1 + + +[[pins]] +pin = 78 +pad_name = "P0.02" +gpio = 2 + + +[[pins]] +pin = 79 +pad_name = "P0.03" +gpio = 3 + + +[[pins]] +pin = 80 +pad_name = "P0.04" +gpio = 4 + + +[[pins]] +pin = 81 +pad_name = "P0.05" +gpio = 5 + + +[[pins]] +pin = 82 +pad_name = "P0.06" +gpio = 6 + + +[[pins]] +pin = 84 +pad_name = "P0.16" +gpio = 16 + + +[[pins]] +pin = 85 +pad_name = "P0.17" +gpio = 17 + + +[[pins]] +pin = 86 +pad_name = "P0.18" +gpio = 18 + + +[[pins]] +pin = 87 +pad_name = "P0.19" +gpio = 19 + + +[[pins]] +pin = 88 +pad_name = "P0.20" +gpio = 20 + + +[[pins]] +pin = 89 +pad_name = "P0.21" +gpio = 21 + + +[[pins]] +pin = 90 +pad_name = "P0.22" +gpio = 22 + + +[[pins]] +pin = 91 +pad_name = "P0.23" +gpio = 23 + + +[[pins]] +pin = 92 +pad_name = "P1.00" +gpio = 32 + + +[[pins]] +pin = 93 +pad_name = "P1.01" +gpio = 33 + + +[[pins]] +pin = 94 +pad_name = "P1.02" +gpio = 34 + + +[[pins]] +pin = 95 +pad_name = "P1.03" +gpio = 35 + + +[[pins]] +pin = 97 +pad_name = "P1.04" +gpio = 36 + + +[[pins]] +pin = 98 +pad_name = "P1.05" +gpio = 37 + + +[[pins]] +pin = 99 +pad_name = "P1.06" +gpio = 38 + + +[[pins]] +pin = 100 +pad_name = "P1.07" +gpio = 39 + + +[[pins]] +pin = 1 +pad_name = "P1.08" +gpio = 40 + + +[[pins]] +pin = 2 +pad_name = "P1.09" +gpio = 41 + + +[[pins]] +pin = 3 +pad_name = "P1.10" +gpio = 42 + + +[[pins]] +pin = 4 +pad_name = "P1.11" +gpio = 43 + + +[[pins]] +pin = 5 +pad_name = "P1.12" +gpio = 44 + + +[[pins]] +pin = 6 +pad_name = "P1.13" +gpio = 45 + + +[[pins]] +pin = 7 +pad_name = "P1.14" +gpio = 46 + + +[[pins]] +pin = 8 +pad_name = "P1.15" +gpio = 47 + + +[[pins]] +pin = 10 +pad_name = "P1.30" +gpio = 62 + + +[[pins]] +pin = 11 +pad_name = "P1.31" +gpio = 63 + + +[[pins]] +pin = 14 +pad_name = "P2.00" +gpio = 64 + + +[[pins]] +pin = 15 +pad_name = "P2.01" +gpio = 65 + + +[[pins]] +pin = 16 +pad_name = "P2.02" +gpio = 66 + + +[[pins]] +pin = 17 +pad_name = "P2.03" +gpio = 67 + + +[[pins]] +pin = 18 +pad_name = "P2.04" +gpio = 68 + + +[[pins]] +pin = 19 +pad_name = "P2.05" +gpio = 69 + + +[[pins]] +pin = 20 +pad_name = "P2.06" +gpio = 70 + + +[[pins]] +pin = 21 +pad_name = "P2.07" +gpio = 71 + + +[[pins]] +pin = 75 +pad_name = "P3.00" +gpio = 96 + + +[[pins]] +pin = 74 +pad_name = "P3.01" +gpio = 97 + + +[[pins]] +pin = 72 +pad_name = "P3.06" +gpio = 102 + + +[[pins]] +pin = 71 +pad_name = "P3.07" +gpio = 103 + + +[[pins]] +pin = 70 +pad_name = "P3.08" +gpio = 104 + + +[[pins]] +pin = 69 +pad_name = "P3.09" +gpio = 105 + + +[[pins]] +pin = 68 +pad_name = "P3.10" +gpio = 106 + + +[[pins]] +pin = 67 +pad_name = "P3.11" +gpio = 107 + + +[[pins]] +pin = 65 +pad_name = "P3.12" +gpio = 108 + + +[[pins]] +pin = 64 +pad_name = "P3.13" +gpio = 109 + + +[[pins]] +pin = 63 +pad_name = "P3.14" +gpio = 110 + + +[[pins]] +pin = 62 +pad_name = "P3.15" +gpio = 111 + + +[[pins]] +pin = 61 +pad_name = "P3.16" +gpio = 112 + + +[[pins]] +pin = 60 +pad_name = "P3.17" +gpio = 113 + + +[[pins]] +pin = 57 +pad_name = "P3.20" +gpio = 116 + + +[[pins]] +pin = 56 +pad_name = "P3.21" +gpio = 117 + + +[[pins]] +pin = 22 +pad_name = "P4.00" +gpio = 128 + + +[[pins]] +pin = 23 +pad_name = "P4.01" +gpio = 129 + + +[[pins]] +pin = 24 +pad_name = "P4.02" +gpio = 130 + + +[[pins]] +pin = 25 +pad_name = "P4.03" +gpio = 131 + + +[[pins]] +pin = 26 +pad_name = "P4.04" +gpio = 132 + + +[[pins]] +pin = 27 +pad_name = "P4.05" +gpio = 133 + + +[[pins]] +pin = 28 +pad_name = "P4.06" +gpio = 134 + + +[[pins]] +pin = 29 +pad_name = "P4.07" +gpio = 135 + + +[[pins]] +pin = 35 +pad_name = "P4.12" +gpio = 140 + + +[[pins]] +pin = 36 +pad_name = "P4.13" +gpio = 141 + + +[[pins]] +pin = 37 +pad_name = "P4.15" +gpio = 143 + + +[[pins]] +pin = 38 +pad_name = "P4.16" +gpio = 144 + + +[[pins]] +pin = 39 +pad_name = "P4.17" +gpio = 145 + + +[[pins]] +pin = 40 +pad_name = "P4.19" +gpio = 147 + + +[[pins]] +pin = 41 +pad_name = "P4.20" +gpio = 148 + + +[[pins]] +pin = 42 +pad_name = "P4.21" +gpio = 149 + + +[[pins]] +pin = 43 +pad_name = "P4.23" +gpio = 151 + + +[[pins]] +pin = 48 +pad_name = "P5.00" +gpio = 160 + + +[[pins]] +pin = 49 +pad_name = "P5.01" +gpio = 161 + + +[[pins]] +pin = 50 +pad_name = "P5.02" +gpio = 162 + + +[[pins]] +pin = 51 +pad_name = "P5.03" +gpio = 163 + + +[[pins]] +pin = 22 +pad_name = "ANA_0" + + +[[pins]] +pin = 23 +pad_name = "ANA_1" + + +[[pins]] +pin = 36 +pad_name = "ANA_14" + + +[[pins]] +pin = 39 +pad_name = "ANA_18" + + +[[pins]] +pin = 26 +pad_name = "ANA_4" + + +[[pins]] +pin = 27 +pad_name = "ANA_5" + + +[[pins]] +pin = 28 +pad_name = "ANA_6" + + +[[pins]] +pin = 29 +pad_name = "ANA_7" diff --git a/ports/zephyr-cp/internal-modules/iobroker/packages/mdbt50q_1mv2.toml b/ports/zephyr-cp/internal-modules/iobroker/packages/mdbt50q_1mv2.toml new file mode 100644 index 00000000000..82c7981337e --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/packages/mdbt50q_1mv2.toml @@ -0,0 +1,410 @@ +# Package pin map for the Raytac MDBT50Q-1MV2 module (nRF52840 aQFN73 +# inside), transcribed from "[nRF52840] MDBT50Q-1MV2 & MDBT50Q-P1MV2_Ver.L +# spec.pdf", section 2.5 Pin Assignment, with a one-off pdftotext -layout +# parse. Review against the datasheet figure before editing by hand. +# +# 'pin' numbers the module's castellated pins as the datasheet numbers them +# (1..61); non-GPIO pins carry no pad. + +name = "mdbt50q_1mv2" +socs = ["SOC_NRF52840_QIAA"] + +[[pins]] +pin = 1 +name = "GND" # non-GPIO + + +[[pins]] +pin = 2 +name = "GND" # non-GPIO + + +[[pins]] +pin = 3 +pad = 7 +pad_name = "P1.10" +gpio = 42 + + +[[pins]] +pin = 4 +pad = 19 +pad_name = "P1.11" +gpio = 43 + + +[[pins]] +pin = 5 +pad = 18 +pad_name = "P1.12" +gpio = 44 + + +[[pins]] +pin = 6 +pad = 5 +pad_name = "P1.13" +gpio = 45 + + +[[pins]] +pin = 7 +pad = 17 +pad_name = "P1.14" +gpio = 46 + + +[[pins]] +pin = 8 +pad = 4 +pad_name = "P1.15" +gpio = 47 + + +[[pins]] +pin = 9 +pad = 16 +pad_name = "P0.03" +gpio = 3 + + +[[pins]] +pin = 10 +pad = 2 +pad_name = "P0.29" +gpio = 29 + + +[[pins]] +pin = 11 +pad = 3 +pad_name = "P0.02" +gpio = 2 + + +[[pins]] +pin = 12 +pad = 1 +pad_name = "P0.31" +gpio = 31 + + +[[pins]] +pin = 13 +pad = 15 +pad_name = "P0.28" +gpio = 28 + + +[[pins]] +pin = 14 +pad = 14 +pad_name = "P0.30" +gpio = 30 + + +[[pins]] +pin = 15 +name = "GND" # non-GPIO + + +[[pins]] +pin = 16 +pad = 28 +pad_name = "P0.27" +gpio = 27 + + +[[pins]] +pin = 17 +pad = 22 +pad_name = "P0.00" +gpio = 0 + + +[[pins]] +pin = 18 +pad = 25 +pad_name = "P0.01" +gpio = 1 + + +[[pins]] +pin = 19 +pad = 27 +pad_name = "P0.26" +gpio = 26 + + +[[pins]] +pin = 20 +pad = 30 +pad_name = "P0.04" +gpio = 4 + + +[[pins]] +pin = 21 +pad = 32 +pad_name = "P0.05" +gpio = 5 + + +[[pins]] +pin = 22 +pad = 33 +pad_name = "P0.06" +gpio = 6 + + +[[pins]] +pin = 23 +pad = 35 +pad_name = "P0.07" +gpio = 7 + + +[[pins]] +pin = 24 +pad = 36 +pad_name = "P0.08" +gpio = 8 + + +[[pins]] +pin = 25 +pad = 38 +pad_name = "P1.08" +gpio = 40 + + +[[pins]] +pin = 26 +pad = 40 +pad_name = "P1.09" +gpio = 41 + + +[[pins]] +pin = 27 +pad = 42 +pad_name = "P0.11" +gpio = 11 + + +[[pins]] +pin = 28 +name = "VDD" # non-GPIO + + +[[pins]] +pin = 29 +pad = 44 +pad_name = "P0.12" +gpio = 12 + + +[[pins]] +pin = 30 +name = "VDD" # non-GPIO + + +[[pins]] +pin = 31 +name = "DCCH" # non-GPIO + + +[[pins]] +pin = 32 +name = "VBUS" # non-GPIO + + +[[pins]] +pin = 33 +name = "GND" # non-GPIO + + +[[pins]] +pin = 34 +name = "D-" # non-GPIO + + +[[pins]] +pin = 35 +name = "D+" # non-GPIO + + +[[pins]] +pin = 36 +pad = 54 +pad_name = "P0.14" +gpio = 14 + + +[[pins]] +pin = 37 +pad = 65 +pad_name = "P0.13" +gpio = 13 + + +[[pins]] +pin = 38 +pad = 55 +pad_name = "P0.16" +gpio = 16 + + +[[pins]] +pin = 39 +pad = 66 +pad_name = "P0.15" +gpio = 15 + + +[[pins]] +pin = 40 +pad = 56 +pad_name = "P0.18" +gpio = 18 + + +[[pins]] +pin = 41 +pad = 67 +pad_name = "P0.17" +gpio = 17 + + +[[pins]] +pin = 42 +pad = 57 +pad_name = "P0.19" +gpio = 19 + + +[[pins]] +pin = 43 +pad = 58 +pad_name = "P0.21" +gpio = 21 + + +[[pins]] +pin = 44 +pad = 69 +pad_name = "P0.20" +gpio = 20 + + +[[pins]] +pin = 45 +pad = 59 +pad_name = "P0.23" +gpio = 23 + + +[[pins]] +pin = 46 +pad = 70 +pad_name = "P0.22" +gpio = 22 + + +[[pins]] +pin = 47 +pad = 72 +pad_name = "P1.00" +gpio = 32 + + +[[pins]] +pin = 48 +pad = 71 +pad_name = "P0.24" +gpio = 24 + + +[[pins]] +pin = 49 +pad = 60 +pad_name = "P0.25" +gpio = 25 + + +[[pins]] +pin = 50 +pad = 48 +pad_name = "P1.02" +gpio = 34 + + +[[pins]] +pin = 51 +name = "SWDIO" # non-GPIO + + +[[pins]] +pin = 52 +pad = 34 +pad_name = "P0.09" +gpio = 9 + + +[[pins]] +pin = 53 +name = "SWDCLK" # non-GPIO + + +[[pins]] +pin = 54 +pad = 31 +pad_name = "P0.10" +gpio = 10 + + +[[pins]] +pin = 55 +name = "GND" # non-GPIO + + +[[pins]] +pin = 56 +pad = 45 +pad_name = "P1.04" +gpio = 36 + + +[[pins]] +pin = 57 +pad = 41 +pad_name = "P1.06" +gpio = 38 + + +[[pins]] +pin = 58 +pad = 39 +pad_name = "P1.07" +gpio = 39 + + +[[pins]] +pin = 59 +pad = 43 +pad_name = "P1.05" +gpio = 37 + + +[[pins]] +pin = 60 +pad = 46 +pad_name = "P1.03" +gpio = 35 + + +[[pins]] +pin = 61 +pad = 50 +pad_name = "P1.01" +gpio = 33 diff --git a/ports/zephyr-cp/internal-modules/iobroker/packages/nrf52840_aqfn73.toml b/ports/zephyr-cp/internal-modules/iobroker/packages/nrf52840_aqfn73.toml new file mode 100644 index 00000000000..936f759115e --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/packages/nrf52840_aqfn73.toml @@ -0,0 +1,345 @@ +# Package pin map for nrf52840_aqfn73, transcribed from nRF52840_PS_v1.1.pdf, +# section 7.1.1 (aQFN73 ball assignments), +# by tools/gen_package.py. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. + +# Ball packages: 'pin' numbers the balls sequentially in row-major +# order (the datasheet only labels them, e.g. B2). + +name = "nrf52840_aqfn73" +socs = ["SOC_NRF52840_QIAA"] + +[[pins]] +pin = 1 +ball = "A8" +pad_name = "P0.31" +gpio = 31 + + +[[pins]] +pin = 2 +ball = "A10" +pad_name = "P0.29" +gpio = 29 + + +[[pins]] +pin = 3 +ball = "A12" +pad_name = "P0.02" +gpio = 2 + + +[[pins]] +pin = 4 +ball = "A14" +pad_name = "P1.15" +gpio = 47 + + +[[pins]] +pin = 5 +ball = "A16" +pad_name = "P1.13" +gpio = 45 + + +[[pins]] +pin = 7 +ball = "A20" +pad_name = "P1.10" +gpio = 42 + + +[[pins]] +pin = 14 +ball = "B9" +pad_name = "P0.30" +gpio = 30 + + +[[pins]] +pin = 15 +ball = "B11" +pad_name = "P0.28" +gpio = 28 + + +[[pins]] +pin = 16 +ball = "B13" +pad_name = "P0.03" +gpio = 3 + + +[[pins]] +pin = 17 +ball = "B15" +pad_name = "P1.14" +gpio = 46 + + +[[pins]] +pin = 18 +ball = "B17" +pad_name = "P1.12" +gpio = 44 + + +[[pins]] +pin = 19 +ball = "B19" +pad_name = "P1.11" +gpio = 43 + + +[[pins]] +pin = 22 +ball = "D2" +pad_name = "P0.00" +gpio = 0 + + +[[pins]] +pin = 25 +ball = "F2" +pad_name = "P0.01" +gpio = 1 + + +[[pins]] +pin = 27 +ball = "G1" +pad_name = "P0.26" +gpio = 26 + + +[[pins]] +pin = 28 +ball = "H2" +pad_name = "P0.27" +gpio = 27 + + +[[pins]] +pin = 30 +ball = "J1" +pad_name = "P0.04" +gpio = 4 + + +[[pins]] +pin = 31 +ball = "J24" +pad_name = "P0.10" +gpio = 10 + + +[[pins]] +pin = 32 +ball = "K2" +pad_name = "P0.05" +gpio = 5 + + +[[pins]] +pin = 33 +ball = "L1" +pad_name = "P0.06" +gpio = 6 + + +[[pins]] +pin = 34 +ball = "L24" +pad_name = "P0.09" +gpio = 9 + + +[[pins]] +pin = 35 +ball = "M2" +pad_name = "P0.07" +gpio = 7 + + +[[pins]] +pin = 36 +ball = "N1" +pad_name = "P0.08" +gpio = 8 + + +[[pins]] +pin = 38 +ball = "P2" +pad_name = "P1.08" +gpio = 40 + + +[[pins]] +pin = 39 +ball = "P23" +pad_name = "P1.07" +gpio = 39 + + +[[pins]] +pin = 40 +ball = "R1" +pad_name = "P1.09" +gpio = 41 + + +[[pins]] +pin = 41 +ball = "R24" +pad_name = "P1.06" +gpio = 38 + + +[[pins]] +pin = 42 +ball = "T2" +pad_name = "P0.11" +gpio = 11 + + +[[pins]] +pin = 43 +ball = "T23" +pad_name = "P1.05" +gpio = 37 + + +[[pins]] +pin = 44 +ball = "U1" +pad_name = "P0.12" +gpio = 12 + + +[[pins]] +pin = 45 +ball = "U24" +pad_name = "P1.04" +gpio = 36 + + +[[pins]] +pin = 46 +ball = "V23" +pad_name = "P1.03" +gpio = 35 + + +[[pins]] +pin = 48 +ball = "W24" +pad_name = "P1.02" +gpio = 34 + + +[[pins]] +pin = 50 +ball = "Y23" +pad_name = "P1.01" +gpio = 33 + + +[[pins]] +pin = 54 +ball = "AC9" +pad_name = "P0.14" +gpio = 14 + + +[[pins]] +pin = 55 +ball = "AC11" +pad_name = "P0.16" +gpio = 16 + + +[[pins]] +pin = 56 +ball = "AC13" +pad_name = "P0.18" +gpio = 18 + + +[[pins]] +pin = 57 +ball = "AC15" +pad_name = "P0.19" +gpio = 19 + + +[[pins]] +pin = 58 +ball = "AC17" +pad_name = "P0.21" +gpio = 21 + + +[[pins]] +pin = 59 +ball = "AC19" +pad_name = "P0.23" +gpio = 23 + + +[[pins]] +pin = 60 +ball = "AC21" +pad_name = "P0.25" +gpio = 25 + + +[[pins]] +pin = 65 +ball = "AD8" +pad_name = "P0.13" +gpio = 13 + + +[[pins]] +pin = 66 +ball = "AD10" +pad_name = "P0.15" +gpio = 15 + + +[[pins]] +pin = 67 +ball = "AD12" +pad_name = "P0.17" +gpio = 17 + + +[[pins]] +pin = 69 +ball = "AD16" +pad_name = "P0.20" +gpio = 20 + + +[[pins]] +pin = 70 +ball = "AD18" +pad_name = "P0.22" +gpio = 22 + + +[[pins]] +pin = 71 +ball = "AD20" +pad_name = "P0.24" +gpio = 24 + + +[[pins]] +pin = 72 +ball = "AD22" +pad_name = "P1.00" +gpio = 32 diff --git a/ports/zephyr-cp/internal-modules/iobroker/packages/nrf5340_qkaa.toml b/ports/zephyr-cp/internal-modules/iobroker/packages/nrf5340_qkaa.toml new file mode 100644 index 00000000000..8cbd9ab1f06 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/packages/nrf5340_qkaa.toml @@ -0,0 +1,345 @@ +# Package pin map for nrf5340_qkaa, transcribed from nRF5340_PS_v1.6.pdf, +# section 9.1.1 (aQFN94 pin assignments), +# by tools/gen_package.py. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. + +# Ball packages: 'pin' numbers the balls sequentially in row-major +# order (the datasheet only labels them, e.g. B2). + +name = "nrf5340_qkaa" +socs = ["SOC_NRF5340_CPUAPP"] + +[[pins]] +pin = 4 +ball = "A17" +pad_name = "P1.13" +gpio = 45 + + +[[pins]] +pin = 16 +ball = "B14" +pad_name = "P1.15" +gpio = 47 + + +[[pins]] +pin = 17 +ball = "B16" +pad_name = "P1.14" +gpio = 46 + + +[[pins]] +pin = 18 +ball = "B18" +pad_name = "P1.12" +gpio = 44 + + +[[pins]] +pin = 19 +ball = "B20" +pad_name = "P1.11" +gpio = 43 + + +[[pins]] +pin = 20 +ball = "B22" +pad_name = "P0.31" +gpio = 31 + + +[[pins]] +pin = 21 +ball = "B24" +pad_name = "P0.30" +gpio = 30 + + +[[pins]] +pin = 39 +ball = "M2" +pad_name = "P1.00" +gpio = 32 + + +[[pins]] +pin = 40 +ball = "N1" +pad_name = "P0.00" +gpio = 0 + + +[[pins]] +pin = 42 +ball = "P2" +pad_name = "P1.01" +gpio = 33 + + +[[pins]] +pin = 43 +ball = "R1" +pad_name = "P0.01" +gpio = 1 + + +[[pins]] +pin = 44 +ball = "R31" +pad_name = "P1.10" +gpio = 42 + + +[[pins]] +pin = 47 +ball = "U31" +pad_name = "P0.29" +gpio = 29 + + +[[pins]] +pin = 48 +ball = "V2" +pad_name = "P0.04" +gpio = 4 + + +[[pins]] +pin = 49 +ball = "W1" +pad_name = "P0.02" +gpio = 2 + + +[[pins]] +pin = 51 +ball = "Y2" +pad_name = "P0.05" +gpio = 5 + + +[[pins]] +pin = 52 +ball = "AA1" +pad_name = "P0.03" +gpio = 3 + + +[[pins]] +pin = 54 +ball = "AB2" +pad_name = "P0.06" +gpio = 6 + + +[[pins]] +pin = 57 +ball = "AD2" +pad_name = "P0.07" +gpio = 7 + + +[[pins]] +pin = 58 +ball = "AE1" +pad_name = "P1.02" +gpio = 34 + + +[[pins]] +pin = 59 +ball = "AE31" +pad_name = "P0.28" +gpio = 28 + + +[[pins]] +pin = 60 +ball = "AF2" +pad_name = "P1.03" +gpio = 35 + + +[[pins]] +pin = 63 +ball = "AH2" +pad_name = "P0.08" +gpio = 8 + + +[[pins]] +pin = 64 +ball = "AJ1" +pad_name = "P0.09" +gpio = 9 + + +[[pins]] +pin = 66 +ball = "AK2" +pad_name = "P0.10" +gpio = 10 + + +[[pins]] +pin = 67 +ball = "AK4" +pad_name = "P0.11" +gpio = 11 + + +[[pins]] +pin = 68 +ball = "AK6" +pad_name = "P0.12" +gpio = 12 + + +[[pins]] +pin = 69 +ball = "AK8" +pad_name = "P0.14" +gpio = 14 + + +[[pins]] +pin = 70 +ball = "AK10" +pad_name = "P0.15" +gpio = 15 + + +[[pins]] +pin = 71 +ball = "AK12" +pad_name = "P0.17" +gpio = 17 + + +[[pins]] +pin = 72 +ball = "AK14" +pad_name = "P0.18" +gpio = 18 + + +[[pins]] +pin = 73 +ball = "AK16" +pad_name = "P0.20" +gpio = 20 + + +[[pins]] +pin = 74 +ball = "AK18" +pad_name = "P0.22" +gpio = 22 + + +[[pins]] +pin = 75 +ball = "AK20" +pad_name = "P0.23" +gpio = 23 + + +[[pins]] +pin = 76 +ball = "AK22" +pad_name = "P1.05" +gpio = 37 + + +[[pins]] +pin = 77 +ball = "AK24" +pad_name = "P1.07" +gpio = 39 + + +[[pins]] +pin = 78 +ball = "AK26" +pad_name = "P1.09" +gpio = 41 + + +[[pins]] +pin = 79 +ball = "AK28" +pad_name = "P0.25" +gpio = 25 + + +[[pins]] +pin = 80 +ball = "AK30" +pad_name = "P0.27" +gpio = 27 + + +[[pins]] +pin = 82 +ball = "AL5" +pad_name = "P0.13" +gpio = 13 + + +[[pins]] +pin = 84 +ball = "AL9" +pad_name = "P0.16" +gpio = 16 + + +[[pins]] +pin = 86 +ball = "AL13" +pad_name = "P0.19" +gpio = 19 + + +[[pins]] +pin = 87 +ball = "AL15" +pad_name = "P0.21" +gpio = 21 + + +[[pins]] +pin = 89 +ball = "AL19" +pad_name = "P1.04" +gpio = 36 + + +[[pins]] +pin = 90 +ball = "AL21" +pad_name = "P1.06" +gpio = 38 + + +[[pins]] +pin = 91 +ball = "AL23" +pad_name = "P1.08" +gpio = 40 + + +[[pins]] +pin = 93 +ball = "AL27" +pad_name = "P0.24" +gpio = 24 + + +[[pins]] +pin = 94 +ball = "AL29" +pad_name = "P0.26" +gpio = 26 diff --git a/ports/zephyr-cp/internal-modules/iobroker/packages/nrf54l15_qfn48.toml b/ports/zephyr-cp/internal-modules/iobroker/packages/nrf54l15_qfn48.toml new file mode 100644 index 00000000000..73a0ae539c8 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/packages/nrf54l15_qfn48.toml @@ -0,0 +1,192 @@ +# Package pin map for nrf54l15_qfn48, transcribed from nRF54L15_nRF54L10_nRF54L05_Datasheet_v1.0.pdf, +# section 10.1.4 (QFN48, QFAA package pin assignments), +# by tools/gen_package.py. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. + +name = "nrf54l15_qfn48" +socs = ["SOC_NRF54L15", "SOC_NRF54L10", "SOC_NRF54L05"] + +[[pins]] +pin = 1 +pad_name = "P1.00" +gpio = 32 + + +[[pins]] +pin = 2 +pad_name = "P1.01" +gpio = 33 + + +[[pins]] +pin = 3 +pad_name = "P1.02" +gpio = 34 + + +[[pins]] +pin = 4 +pad_name = "P1.03" +gpio = 35 + + +[[pins]] +pin = 5 +pad_name = "P1.04" +gpio = 36 + + +[[pins]] +pin = 6 +pad_name = "P1.05" +gpio = 37 + + +[[pins]] +pin = 7 +pad_name = "P1.06" +gpio = 38 + + +[[pins]] +pin = 8 +pad_name = "P1.07" +gpio = 39 + + +[[pins]] +pin = 9 +pad_name = "P1.08" +gpio = 40 + + +[[pins]] +pin = 11 +pad_name = "P2.00" +gpio = 64 + + +[[pins]] +pin = 12 +pad_name = "P2.01" +gpio = 65 + + +[[pins]] +pin = 13 +pad_name = "P2.02" +gpio = 66 + + +[[pins]] +pin = 14 +pad_name = "P2.03" +gpio = 67 + + +[[pins]] +pin = 15 +pad_name = "P2.04" +gpio = 68 + + +[[pins]] +pin = 16 +pad_name = "P2.05" +gpio = 69 + + +[[pins]] +pin = 17 +pad_name = "P2.06" +gpio = 70 + + +[[pins]] +pin = 18 +pad_name = "P2.07" +gpio = 71 + + +[[pins]] +pin = 19 +pad_name = "P2.08" +gpio = 72 + + +[[pins]] +pin = 20 +pad_name = "P2.09" +gpio = 73 + + +[[pins]] +pin = 21 +pad_name = "P2.10" +gpio = 74 + + +[[pins]] +pin = 23 +pad_name = "P0.00" +gpio = 0 + + +[[pins]] +pin = 24 +pad_name = "P0.01" +gpio = 1 + + +[[pins]] +pin = 27 +pad_name = "P0.02" +gpio = 2 + + +[[pins]] +pin = 28 +pad_name = "P0.03" +gpio = 3 + + +[[pins]] +pin = 29 +pad_name = "P0.04" +gpio = 4 + + +[[pins]] +pin = 37 +pad_name = "P1.09" +gpio = 41 + + +[[pins]] +pin = 38 +pad_name = "P1.10" +gpio = 42 + + +[[pins]] +pin = 39 +pad_name = "P1.11" +gpio = 43 + + +[[pins]] +pin = 40 +pad_name = "P1.12" +gpio = 44 + + +[[pins]] +pin = 41 +pad_name = "P1.13" +gpio = 45 + + +[[pins]] +pin = 42 +pad_name = "P1.14" +gpio = 46 diff --git a/ports/zephyr-cp/internal-modules/iobroker/packages/nrf54lm20_csp98.toml b/ports/zephyr-cp/internal-modules/iobroker/packages/nrf54lm20_csp98.toml new file mode 100644 index 00000000000..31b0bd32314 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/packages/nrf54lm20_csp98.toml @@ -0,0 +1,471 @@ +# Package pin map for nrf54lm20_csp98, transcribed from nRF54LM20A_nRF54LM20B_Datasheet_v1.0.pdf, +# section 10.1.4 (CSP98, PAAA package pin assignments), +# by tools/gen_package.py. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. + +# Ball packages: 'pin' numbers the balls sequentially in row-major +# order (the datasheet only labels them, e.g. B2). + +name = "nrf54lm20_csp98" +socs = ["SOC_NRF54LM20A", "SOC_NRF54LM20B"] + +[[pins]] +pin = 12 +ball = "B2" +pad_name = "P1.15" +gpio = 47 + + +[[pins]] +pin = 13 +ball = "B3" +pad_name = "P1.16" +gpio = 48 + + +[[pins]] +pin = 14 +ball = "B4" +pad_name = "P1.17" +gpio = 49 + + +[[pins]] +pin = 15 +ball = "B5" +pad_name = "P1.18" +gpio = 50 + + +[[pins]] +pin = 16 +ball = "B6" +pad_name = "P1.19" +gpio = 51 + + +[[pins]] +pin = 17 +ball = "B7" +pad_name = "P1.23" +gpio = 55 + + +[[pins]] +pin = 18 +ball = "B8" +pad_name = "P1.26" +gpio = 58 + + +[[pins]] +pin = 19 +ball = "B9" +pad_name = "P2.06" +gpio = 70 + + +[[pins]] +pin = 20 +ball = "B10" +pad_name = "P2.00" +gpio = 64 + + +[[pins]] +pin = 22 +ball = "C2" +pad_name = "P1.14" +gpio = 46 + + +[[pins]] +pin = 23 +ball = "C3" +pad_name = "P1.13" +gpio = 45 + + +[[pins]] +pin = 24 +ball = "C4" +pad_name = "P1.12" +gpio = 44 + + +[[pins]] +pin = 25 +ball = "C5" +pad_name = "P1.11" +gpio = 43 + + +[[pins]] +pin = 26 +ball = "C6" +pad_name = "P1.20" +gpio = 52 + + +[[pins]] +pin = 27 +ball = "C7" +pad_name = "P1.21" +gpio = 53 + + +[[pins]] +pin = 28 +ball = "C8" +pad_name = "P1.27" +gpio = 59 + + +[[pins]] +pin = 29 +ball = "C9" +pad_name = "P2.07" +gpio = 71 + + +[[pins]] +pin = 30 +ball = "C10" +pad_name = "P2.01" +gpio = 65 + + +[[pins]] +pin = 33 +ball = "D4" +pad_name = "P3.12" +gpio = 108 + + +[[pins]] +pin = 34 +ball = "D5" +pad_name = "P1.10" +gpio = 42 + + +[[pins]] +pin = 35 +ball = "D6" +pad_name = "P1.22" +gpio = 54 + + +[[pins]] +pin = 36 +ball = "D7" +pad_name = "P1.24" +gpio = 56 + + +[[pins]] +pin = 37 +ball = "D8" +pad_name = "P1.28" +gpio = 60 + + +[[pins]] +pin = 38 +ball = "D9" +pad_name = "P2.08" +gpio = 72 + + +[[pins]] +pin = 39 +ball = "D10" +pad_name = "P2.02" +gpio = 66 + + +[[pins]] +pin = 41 +ball = "E3" +pad_name = "P0.09" +gpio = 9 + + +[[pins]] +pin = 42 +ball = "E4" +pad_name = "P3.11" +gpio = 107 + + +[[pins]] +pin = 45 +ball = "E7" +pad_name = "P1.25" +gpio = 57 + + +[[pins]] +pin = 46 +ball = "E8" +pad_name = "P1.09" +gpio = 41 + + +[[pins]] +pin = 47 +ball = "E9" +pad_name = "P2.09" +gpio = 73 + + +[[pins]] +pin = 48 +ball = "E10" +pad_name = "P2.03" +gpio = 67 + + +[[pins]] +pin = 51 +ball = "F3" +pad_name = "P0.08" +gpio = 8 + + +[[pins]] +pin = 52 +ball = "F4" +pad_name = "P3.10" +gpio = 106 + + +[[pins]] +pin = 55 +ball = "F7" +pad_name = "P3.09" +gpio = 105 + + +[[pins]] +pin = 56 +ball = "F8" +pad_name = "P1.08" +gpio = 40 + + +[[pins]] +pin = 57 +ball = "F9" +pad_name = "P2.10" +gpio = 74 + + +[[pins]] +pin = 58 +ball = "F10" +pad_name = "P2.04" +gpio = 68 + + +[[pins]] +pin = 61 +ball = "G3" +pad_name = "P0.07" +gpio = 7 + + +[[pins]] +pin = 62 +ball = "G4" +pad_name = "P3.08" +gpio = 104 + + +[[pins]] +pin = 63 +ball = "G5" +pad_name = "P3.07" +gpio = 103 + + +[[pins]] +pin = 64 +ball = "G6" +pad_name = "P3.06" +gpio = 102 + + +[[pins]] +pin = 65 +ball = "G7" +pad_name = "P3.05" +gpio = 101 + + +[[pins]] +pin = 66 +ball = "G8" +pad_name = "P1.07" +gpio = 39 + + +[[pins]] +pin = 67 +ball = "G9" +pad_name = "P2.05" +gpio = 69 + + +[[pins]] +pin = 70 +ball = "H2" +pad_name = "P0.06" +gpio = 6 + + +[[pins]] +pin = 71 +ball = "H3" +pad_name = "P0.05" +gpio = 5 + + +[[pins]] +pin = 72 +ball = "H4" +pad_name = "P3.03" +gpio = 99 + + +[[pins]] +pin = 73 +ball = "H5" +pad_name = "P3.02" +gpio = 98 + + +[[pins]] +pin = 74 +ball = "H6" +pad_name = "P3.01" +gpio = 97 + + +[[pins]] +pin = 75 +ball = "H7" +pad_name = "P3.00" +gpio = 96 + + +[[pins]] +pin = 76 +ball = "H8" +pad_name = "P1.05" +gpio = 37 + + +[[pins]] +pin = 77 +ball = "H9" +pad_name = "P1.03" +gpio = 35 + + +[[pins]] +pin = 78 +ball = "H10" +pad_name = "P1.29" +gpio = 61 + + +[[pins]] +pin = 79 +ball = "J1" +pad_name = "P0.04" +gpio = 4 + + +[[pins]] +pin = 80 +ball = "J2" +pad_name = "P0.03" +gpio = 3 + + +[[pins]] +pin = 81 +ball = "J3" +pad_name = "P0.02" +gpio = 2 + + +[[pins]] +pin = 82 +ball = "J4" +pad_name = "P3.04" +gpio = 100 + + +[[pins]] +pin = 84 +ball = "J6" +pad_name = "P1.01" +gpio = 33 + + +[[pins]] +pin = 85 +ball = "J7" +pad_name = "P1.02" +gpio = 34 + + +[[pins]] +pin = 86 +ball = "J8" +pad_name = "P1.06" +gpio = 38 + + +[[pins]] +pin = 87 +ball = "J9" +pad_name = "P1.04" +gpio = 36 + + +[[pins]] +pin = 88 +ball = "J10" +pad_name = "P1.30" +gpio = 62 + + +[[pins]] +pin = 90 +ball = "K2" +pad_name = "P0.01" +gpio = 1 + + +[[pins]] +pin = 91 +ball = "K3" +pad_name = "P0.00" +gpio = 0 + + +[[pins]] +pin = 96 +ball = "K8" +pad_name = "P1.00" +gpio = 32 + + +[[pins]] +pin = 97 +ball = "K9" +pad_name = "P1.31" +gpio = 63 diff --git a/ports/zephyr-cp/internal-modules/iobroker/packages/rp2040_qfn56.toml b/ports/zephyr-cp/internal-modules/iobroker/packages/rp2040_qfn56.toml new file mode 100644 index 00000000000..0d21b1448f9 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/packages/rp2040_qfn56.toml @@ -0,0 +1,188 @@ +# Package pin map for rp2040_qfn56, transcribed from rp2040-datasheet.pdf, +# Table 615 (GPIO pins), with a one-off pdftotext -layout parse. Review +# against the datasheet figure before editing by hand. +# +# RP2040 has a single GPIO port; pads are numbered 0-29 and pad_name uses +# the datasheet's GPIOx naming. + +name = "rp2040_qfn56" +socs = ["SOC_RP2040"] + +[[pins]] +pin = 2 +pad_name = "GPIO0" +gpio = 0 + + +[[pins]] +pin = 3 +pad_name = "GPIO1" +gpio = 1 + + +[[pins]] +pin = 4 +pad_name = "GPIO2" +gpio = 2 + + +[[pins]] +pin = 5 +pad_name = "GPIO3" +gpio = 3 + + +[[pins]] +pin = 6 +pad_name = "GPIO4" +gpio = 4 + + +[[pins]] +pin = 7 +pad_name = "GPIO5" +gpio = 5 + + +[[pins]] +pin = 8 +pad_name = "GPIO6" +gpio = 6 + + +[[pins]] +pin = 9 +pad_name = "GPIO7" +gpio = 7 + + +[[pins]] +pin = 11 +pad_name = "GPIO8" +gpio = 8 + + +[[pins]] +pin = 12 +pad_name = "GPIO9" +gpio = 9 + + +[[pins]] +pin = 13 +pad_name = "GPIO10" +gpio = 10 + + +[[pins]] +pin = 14 +pad_name = "GPIO11" +gpio = 11 + + +[[pins]] +pin = 15 +pad_name = "GPIO12" +gpio = 12 + + +[[pins]] +pin = 16 +pad_name = "GPIO13" +gpio = 13 + + +[[pins]] +pin = 17 +pad_name = "GPIO14" +gpio = 14 + + +[[pins]] +pin = 18 +pad_name = "GPIO15" +gpio = 15 + + +[[pins]] +pin = 27 +pad_name = "GPIO16" +gpio = 16 + + +[[pins]] +pin = 28 +pad_name = "GPIO17" +gpio = 17 + + +[[pins]] +pin = 29 +pad_name = "GPIO18" +gpio = 18 + + +[[pins]] +pin = 30 +pad_name = "GPIO19" +gpio = 19 + + +[[pins]] +pin = 31 +pad_name = "GPIO20" +gpio = 20 + + +[[pins]] +pin = 32 +pad_name = "GPIO21" +gpio = 21 + + +[[pins]] +pin = 34 +pad_name = "GPIO22" +gpio = 22 + + +[[pins]] +pin = 35 +pad_name = "GPIO23" +gpio = 23 + + +[[pins]] +pin = 36 +pad_name = "GPIO24" +gpio = 24 + + +[[pins]] +pin = 37 +pad_name = "GPIO25" +gpio = 25 + + +[[pins]] +pin = 38 +pad_name = "GPIO26" +gpio = 26 + + +[[pins]] +pin = 39 +pad_name = "GPIO27" +gpio = 27 + + +[[pins]] +pin = 40 +pad_name = "GPIO28" +gpio = 28 + + +[[pins]] +pin = 41 +pad_name = "GPIO29" +gpio = 29 diff --git a/ports/zephyr-cp/internal-modules/iobroker/src/emul/iobroker_analog.c b/ports/zephyr-cp/internal-modules/iobroker/src/emul/iobroker_analog.c new file mode 100644 index 00000000000..c469f1d74cc --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/src/emul/iobroker_analog.c @@ -0,0 +1,149 @@ +// Emulated analog pad allocation for the iobroker module (native_sim). +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-License-Identifier: MIT + +// The emulated ADC (zephyr,adc-emul) and the test DAC (vnd,dac) have no +// analog mux: the emulated channel a pad feeds is the pad number itself, and +// the pads are plain GPIO controller pads on the one-to-one package map +// native_sim selects. So allocate resolves the channel from the pad's global +// number and claims the pad; release only drops the claim (the emulated +// devices need no channel teardown). + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "iobroker_internal.h" + +LOG_MODULE_DECLARE(iobroker, CONFIG_LOG_DEFAULT_LEVEL); + +// Emulated devices: one instance each. Additional instances are ignored +// (there are none in practice on native_sim). + +#if IOBROKER_ADC +#if defined(CONFIG_ADC_EMUL) && DT_HAS_COMPAT_STATUS_OKAY(zephyr_adc_emul) +#define ANALOG_ADC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_adc_emul) +static const struct device *analog_adc_device(void) { + return DEVICE_DT_GET(ANALOG_ADC_NODE); +} + +static uint8_t analog_adc_channel_count(void) { + return DT_PROP(ANALOG_ADC_NODE, nchannels); +} +#else +static const struct device *analog_adc_device(void) { + return NULL; +} + +static uint8_t analog_adc_channel_count(void) { + return 0; +} +#endif +#endif // IOBROKER_ADC + +#if IOBROKER_DAC +#if defined(CONFIG_DAC_TEST) && DT_HAS_COMPAT_STATUS_OKAY(vnd_dac) +#define ANALOG_DAC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(vnd_dac) +static const struct device *analog_dac_device(void) { + return DEVICE_DT_GET(ANALOG_DAC_NODE); +} + +// The test DAC's binding has no channel count; the emulated hardware takes +// any channel id, so the number of channels is not bounded beyond the claim +// registry itself. +#define ANALOG_DAC_CHANNEL_COUNT 8 +#else +static const struct device *analog_dac_device(void) { + return NULL; +} + +#define ANALOG_DAC_CHANNEL_COUNT 0 +#endif +#endif // IOBROKER_DAC + +#if IOBROKER_ADC || IOBROKER_DAC +static int adc_dac_allocate(package_pin_t pin, const struct device *dev, + uint8_t count, const struct device **dev_out, uint8_t *channel_out, + uint8_t *input_out, const char *what) { + if (dev == NULL || count == 0) { + return -ENOSYS; + } + + uint16_t soc_pad; + int ret = iobroker_package_pin_soc_pad(pin, &soc_pad); + if (ret < 0) { + LOG_WRN("%s allocate: package pin %u not in package map", + what, (unsigned)pin); + return ret; + } + + // Emulated devices have no analog mux: the channel a pad feeds is the + // pad's global number itself, while it indexes into the device's + // channel array. + if (soc_pad >= count) { + LOG_WRN("%s allocate: pad %u has no analog channel on %s", + what, (unsigned)soc_pad, dev->name); + return -EINVAL; + } + *channel_out = (uint8_t)soc_pad; + + ret = iobroker_analog_claim_add(pin, dev, *channel_out); + if (ret < 0) { + return ret; + } + + *dev_out = dev; + // The emulated devices have no selectable inputs; the caller ignores + // this when it configures the channel. + *input_out = 0; + LOG_INF("%s allocate: package pin %u (pad %u) -> %s channel %u", + what, (unsigned)pin, (unsigned)soc_pad, dev->name, + (unsigned)*channel_out); + return 0; +} + +static bool adc_dac_release(const struct device *dev, uint8_t channel, + const char *what) { + uint16_t soc_pad; + if (!iobroker_analog_claim_remove(dev, channel, &soc_pad)) { + LOG_DBG("%s release: %s channel %u not allocated", + what, dev == NULL ? "(null)" : dev->name, (unsigned)channel); + return false; + } + + iobroker_gpio_pad_quiesce(soc_pad); + LOG_INF("%s release: %s channel %u released", what, dev->name, + (unsigned)channel); + return true; +} + +int iobroker_adc_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + return adc_dac_allocate(pin, analog_adc_device(), analog_adc_channel_count(), + dev_out, channel_out, input_out, "adc"); +} + +bool iobroker_adc_release(const struct device *dev, uint8_t channel) { + return adc_dac_release(dev, channel, "adc"); +} +#endif // IOBROKER_ADC || IOBROKER_DAC + +#if IOBROKER_DAC +int iobroker_dac_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + return adc_dac_allocate(pin, analog_dac_device(), ANALOG_DAC_CHANNEL_COUNT, + dev_out, channel_out, input_out, "dac"); +} + +bool iobroker_dac_release(const struct device *dev, uint8_t channel) { + return adc_dac_release(dev, channel, "dac"); +} +#endif // IOBROKER_DAC diff --git a/ports/zephyr-cp/internal-modules/iobroker/src/iobroker.c b/ports/zephyr-cp/internal-modules/iobroker/src/iobroker.c new file mode 100644 index 00000000000..eea91f14524 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/src/iobroker.c @@ -0,0 +1,437 @@ +// iobroker: dynamic peripheral allocation and runtime pin routing. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +// SoC-agnostic core: package pin resolution, GPIO controller lookup and the +// claim registry. The bus allocate/release functions are SoC-specific and +// live in src///; the -ENOSYS stubs below stand in for SoCs +// with no implementation. + +#include +#include + +#include +#include + +#include "iobroker_internal.h" + +// One log instance for the whole module; iobroker_route.c declares it. +// Compiles away when CONFIG_LOG is off. +LOG_MODULE_REGISTER(iobroker, CONFIG_LOG_DEFAULT_LEVEL); + +int iobroker_gpio_split(uint16_t number, const struct device **port_out, + gpio_pin_t *pin_out) { + for (size_t i = 0; i < iobroker_gpio_port_count; i++) { + uint32_t base = (uint32_t)iobroker_gpio_port_indexes[i] * 32U; + if (number >= base && number < base + 32U) { + *port_out = iobroker_gpio_port_devices[i]; + *pin_out = (gpio_pin_t)(number - base); + return 0; + } + } + return -EINVAL; +} + +int iobroker_gpio_package_pin(uint8_t port, gpio_pin_t pin, + package_pin_t *package_pin_out) { + uint16_t gpio_pad = (uint16_t)((uint32_t)port * 32U + pin); + #if defined(CONFIG_IOBROKER_PACKAGE_ONE_TO_ONE) + // Identity map: the package pin is the global pin number. + *package_pin_out = gpio_pad; + return 0; + #else + for (size_t i = 0; i < iobroker_package_pin_count; i++) { + if (iobroker_package_pins[i].gpio_pad == gpio_pad) { + *package_pin_out = iobroker_package_pins[i].package_pin; + return 0; + } + } + return -EINVAL; + #endif +} + +int iobroker_package_pin_soc_pad(package_pin_t pin, uint16_t *soc_pad_out) { + if (pin == IOBROKER_NO_PIN) { + *soc_pad_out = IOBROKER_NO_PIN; + return 0; + } + #if defined(CONFIG_IOBROKER_PACKAGE_ONE_TO_ONE) + // Identity map: the package pin is the global pin number. + *soc_pad_out = pin; + return 0; + #else + for (size_t i = 0; i < iobroker_package_pin_count; i++) { + if (iobroker_package_pins[i].package_pin == pin) { + *soc_pad_out = iobroker_package_pins[i].soc_pad; + return 0; + } + } + return -EINVAL; + #endif +} + +int iobroker_pad_gpio(uint16_t soc_pad, uint16_t *gpio_pad_out) { + if (soc_pad == IOBROKER_NO_PIN) { + *gpio_pad_out = IOBROKER_NO_PIN; + return 0; + } + #if defined(CONFIG_IOBROKER_PACKAGE_ONE_TO_ONE) + // Identity map: the package pin is the global pin number, so every pad + // sits on its GPIO controller. + *gpio_pad_out = soc_pad; + return 0; + #else + for (size_t i = 0; i < iobroker_package_pin_count; i++) { + if (iobroker_package_pins[i].soc_pad == soc_pad) { + if (iobroker_package_pins[i].gpio_pad == IOBROKER_PAD_NO_GPIO) { + // Analog-only pad: no GPIO controller bond. + return -EINVAL; + } + *gpio_pad_out = iobroker_package_pins[i].gpio_pad; + return 0; + } + } + return -EINVAL; + #endif +} + +#if !IOBROKER_ROUTING + +// SoCs without runtime routing: the bus allocate/release API still exists so +// busio can call it, but every allocate reports -ENOSYS. The implementations +// for routing SoCs live in src///. +int iobroker_i2c_allocate(package_pin_t sda, package_pin_t scl, + const struct device **dev_out) { + (void)sda; + (void)scl; + (void)dev_out; + return -ENOSYS; +} + +int iobroker_spi_allocate(package_pin_t clock, package_pin_t mosi, + package_pin_t miso, const struct device **dev_out) { + (void)clock; + (void)mosi; + (void)miso; + (void)dev_out; + return -ENOSYS; +} + +int iobroker_uart_allocate(package_pin_t tx, package_pin_t rx, + package_pin_t rts, package_pin_t cts, const struct device **dev_out) { + (void)tx; + (void)rx; + (void)rts; + (void)cts; + (void)dev_out; + return -ENOSYS; +} + +bool iobroker_release(const struct device *dev) { + (void)dev; + LOG_DBG("release: no routing support on this SoC, nothing to release"); + return false; +} + +#endif // !IOBROKER_ROUTING + +#if !IOBROKER_ADC + +// SoCs without an ADC implementation: the ADC allocate/release API still +// exists so analogio can call it, but every call reports -ENOSYS. The +// implementations live in src/// (nRF SAADC) and in src/emul/ +// (the emulated ADC). +int iobroker_adc_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + (void)pin; + (void)dev_out; + (void)channel_out; + (void)input_out; + return -ENOSYS; +} + +bool iobroker_adc_release(const struct device *dev, uint8_t channel) { + (void)dev; + (void)channel; + LOG_DBG("adc release: no ADC support on this SoC, nothing to release"); + return false; +} + +#endif // !IOBROKER_ADC + +#if !IOBROKER_DAC + +// SoCs without a DAC implementation: the DAC allocate/release API still +// exists so analogio can call it, but every call reports -ENOSYS. The +// implementations live in src/// (Renesas RA DAC) and in +// src/emul/ (the emulated test DAC). +int iobroker_dac_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + (void)pin; + (void)dev_out; + (void)channel_out; + (void)input_out; + return -ENOSYS; +} + +bool iobroker_dac_release(const struct device *dev, uint8_t channel) { + (void)dev; + (void)channel; + LOG_DBG("dac release: no DAC support on this SoC, nothing to release"); + return false; +} + +#endif // !IOBROKER_DAC + +// Pins currently claimed for plain GPIO use. The module leaves the pad alone +// on allocate (the caller configures it) but returns it to a quiescent state +// on release. Claims store the GPIO controller device and pin number that the +// allocate call resolved and returned. +typedef struct { + const struct device *port; + gpio_pin_t number; + bool in_use; +} gpio_claim_t; + +static gpio_claim_t gpio_claims[CONFIG_IOBROKER_GPIO_MAX_PINS]; + +// Pads currently claimed for analog use (ADC inputs, DAC outputs). Analog +// functions have no runtime routing; the claim only marks the pad busy for +// bus and GPIO allocations, and records which analog device and channel slot +// the allocation holds. The implementations keep the registry consistent +// through the claim_add/claim_remove helpers below. +typedef struct { + const struct device *dev; + uint8_t channel; + uint16_t soc_pad; + bool in_use; +} analog_claim_t; + +static analog_claim_t analog_claims[CONFIG_IOBROKER_ANALOG_MAX_PINS]; + +// Returns true when the package pin is claimed by a currently allocated +// instance or a GPIO allocation. Disconnected signals (IOBROKER_NO_PIN) +// and package pins with no entry in the map claim nothing. The bus loops +// below exist whenever the nRF pinctrl driver is built (the board emits the +// instance tables); without routing the tables are never marked in use. +bool iobroker_pin_in_use(package_pin_t pin) { + if (pin == IOBROKER_NO_PIN) { + return false; + } + #if defined(CONFIG_PINCTRL_NRF) + for (size_t i = 0; i < iobroker_i2c_bus_count; i++) { + if (!iobroker_i2c_bus_states[i].in_use) { + continue; + } + for (uint8_t j = 0; j < iobroker_i2c_bus_states[i].pin_count; j++) { + if (iobroker_i2c_bus_states[i].pins[j] == pin) { + return true; + } + } + } + for (size_t i = 0; i < iobroker_spi_bus_count; i++) { + if (!iobroker_spi_bus_states[i].in_use) { + continue; + } + for (uint8_t j = 0; j < iobroker_spi_bus_states[i].pin_count; j++) { + if (iobroker_spi_bus_states[i].pins[j] == pin) { + return true; + } + } + } + for (size_t i = 0; i < iobroker_uart_bus_count; i++) { + if (!iobroker_uart_bus_states[i].in_use) { + continue; + } + for (uint8_t j = 0; j < iobroker_uart_bus_states[i].pin_count; j++) { + if (iobroker_uart_bus_states[i].pins[j] == pin) { + return true; + } + } + } + #endif // CONFIG_PINCTRL_NRF + uint16_t soc_pad; + if (iobroker_package_pin_soc_pad(pin, &soc_pad) < 0) { + return false; + } + #if defined(CONFIG_PINCTRL_NRF) + // Pads owned by fixed peripherals (console UART, flash instance, ...) are + // always in use: their pinctrl state drives them from boot. + for (size_t i = 0; i < iobroker_reserved_pads_count; i++) { + if (iobroker_reserved_pads[i] == soc_pad) { + return true; + } + } + #endif + // Analog claims don't involve GPIO controllers (the analog input is a + // fixed mux setting of the SoC), so match by pad. Analog-only pads, which + // no GPIO controller covers, are reported through this loop. + for (size_t i = 0; i < ARRAY_SIZE(analog_claims); i++) { + if (analog_claims[i].in_use && analog_claims[i].soc_pad == soc_pad) { + return true; + } + } + uint16_t gpio_pad; + if (iobroker_pad_gpio(soc_pad, &gpio_pad) < 0) { + return false; + } + const struct device *port; + gpio_pin_t number; + if (iobroker_gpio_split(gpio_pad, &port, &number) < 0) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(gpio_claims); i++) { + if (gpio_claims[i].in_use && gpio_claims[i].port == port && + gpio_claims[i].number == number) { + return true; + } + } + return false; +} + +// Claim a package pin for GPIO use. The caller configures the pad itself +// while the claim is held; release returns it to a quiescent state. The pin +// is resolved through the package pin map, and the GPIO controller device and +// pin number within it are returned. +int iobroker_gpio_allocate(package_pin_t pin, + const struct device **port_out, gpio_pin_t *pin_out) { + if (pin == IOBROKER_NO_PIN) { + LOG_WRN("gpio allocate: pin is disconnected"); + return -EINVAL; + } + if (iobroker_pin_in_use(pin)) { + LOG_WRN("gpio allocate: package pin %u already claimed", (unsigned)pin); + return -EBUSY; + } + uint16_t soc_pad; + int ret = iobroker_package_pin_soc_pad(pin, &soc_pad); + if (ret < 0) { + LOG_WRN("gpio allocate: package pin %u not in package map", (unsigned)pin); + return ret; + } + uint16_t gpio_pad; + ret = iobroker_pad_gpio(soc_pad, &gpio_pad); + if (ret < 0) { + LOG_WRN("gpio allocate: no GPIO controller for pad %u", (unsigned)soc_pad); + return ret; + } + ret = iobroker_gpio_split(gpio_pad, port_out, pin_out); + if (ret < 0) { + LOG_WRN("gpio allocate: no GPIO controller for pad %u", (unsigned)soc_pad); + return ret; + } + for (size_t i = 0; i < ARRAY_SIZE(gpio_claims); i++) { + if (gpio_claims[i].in_use) { + continue; + } + gpio_claims[i].port = *port_out; + gpio_claims[i].number = *pin_out; + gpio_claims[i].in_use = true; + LOG_DBG("gpio allocate: package pin %u -> %s pin %u", + (unsigned)pin, (*port_out)->name, (unsigned)*pin_out); + return 0; + } + LOG_WRN("gpio allocate: claim registry full (%u pins)", + (unsigned)CONFIG_IOBROKER_GPIO_MAX_PINS); + return -ENOMEM; +} + +// Return a pad to a quiescent state: disconnected from any peripheral +// routing, input buffer and driver off, no pulls. +static void gpio_deconfigure(const struct device *port, gpio_pin_t number) { + if (gpio_pin_configure(port, number, GPIO_DISCONNECTED) == -ENOTSUP) { + // SoCs without GPIO_DISCONNECTED support settle for a plain input. + gpio_pin_configure(port, number, GPIO_INPUT); + } +} + +// Release a GPIO claim and return the pad to a quiescent state. Pass the +// device and pin number that the allocate call returned. Returns true when a +// claim was held. +bool iobroker_gpio_release(const struct device *port, gpio_pin_t number) { + for (size_t i = 0; i < ARRAY_SIZE(gpio_claims); i++) { + if (gpio_claims[i].in_use && gpio_claims[i].port == port && + gpio_claims[i].number == number) { + gpio_claims[i].in_use = false; + gpio_deconfigure(port, number); + LOG_DBG("gpio release: %s pin %u back to quiescent state", + port->name, (unsigned)number); + return true; + } + } + return false; +} + +// Shared analog claim registry helpers, used by the analog implementations. +// See iobroker_internal.h for the contract. + +bool iobroker_analog_channel_in_use(const struct device *dev, uint8_t channel) { + for (size_t i = 0; i < ARRAY_SIZE(analog_claims); i++) { + if (analog_claims[i].in_use && analog_claims[i].dev == dev && + analog_claims[i].channel == channel) { + return true; + } + } + return false; +} + +int iobroker_analog_claim_add(package_pin_t pin, const struct device *dev, + uint8_t channel) { + if (iobroker_pin_in_use(pin)) { + LOG_WRN("analog claim: package pin %u already claimed", (unsigned)pin); + return -EBUSY; + } + uint16_t soc_pad; + int ret = iobroker_package_pin_soc_pad(pin, &soc_pad); + if (ret < 0) { + LOG_WRN("analog claim: package pin %u not in package map", (unsigned)pin); + return ret; + } + for (size_t i = 0; i < ARRAY_SIZE(analog_claims); i++) { + if (analog_claims[i].in_use) { + continue; + } + analog_claims[i].dev = dev; + analog_claims[i].channel = channel; + analog_claims[i].soc_pad = soc_pad; + analog_claims[i].in_use = true; + LOG_DBG("analog claim: package pin %u (pad %u) -> %s channel %u", + (unsigned)pin, (unsigned)soc_pad, dev->name, (unsigned)channel); + return 0; + } + LOG_WRN("analog claim: registry full (%u pads)", + (unsigned)CONFIG_IOBROKER_ANALOG_MAX_PINS); + return -ENOMEM; +} + +bool iobroker_analog_claim_remove(const struct device *dev, uint8_t channel, + uint16_t *soc_pad_out) { + for (size_t i = 0; i < ARRAY_SIZE(analog_claims); i++) { + if (analog_claims[i].in_use && analog_claims[i].dev == dev && + analog_claims[i].channel == channel) { + analog_claims[i].in_use = false; + *soc_pad_out = analog_claims[i].soc_pad; + return true; + } + } + return false; +} + +void iobroker_gpio_pad_quiesce(uint16_t soc_pad) { + uint16_t gpio_pad; + // Analog-only pads, which no GPIO controller covers, have no digital + // configuration to quiesce. + if (iobroker_pad_gpio(soc_pad, &gpio_pad) < 0) { + return; + } + const struct device *port; + gpio_pin_t number; + if (iobroker_gpio_split(gpio_pad, &port, &number) < 0) { + return; + } + gpio_deconfigure(port, number); +} diff --git a/ports/zephyr-cp/internal-modules/iobroker/src/iobroker_internal.h b/ports/zephyr-cp/internal-modules/iobroker/src/iobroker_internal.h new file mode 100644 index 00000000000..6b74dafcebe --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/src/iobroker_internal.h @@ -0,0 +1,52 @@ +// iobroker internal helpers shared between the SoC-agnostic core and the +// vendor/SoC implementations in src///. Not part of the public +// API; see include/iobroker/iobroker.h for that. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +// Resolve a package pin to the SoC pad it is bonded to (defined by the core). +// IOBROKER_NO_PIN passes through unchanged so that disconnected optional +// signals stay disconnected. Returns 0, or -EINVAL when the pin is not in +// the map. +int iobroker_package_pin_soc_pad(package_pin_t pin, uint16_t *soc_pad_out); + +// Resolve a SoC pad to the global GPIO number it is bonded to (port index * +// 32 + pin). Returns 0, or -EINVAL when the pad has no GPIO controller (an +// analog-only pad). +int iobroker_pad_gpio(uint16_t soc_pad, uint16_t *gpio_pad_out); + +// Analog claim registry helpers, shared by the analog implementations in +// src/// and src/emul/. The registry is what +// iobroker_pin_in_use() reports, so bus allocate() calls and GPIO +// allocations refuse analog-claimed pads. + +// Returns true when the channel slot is currently taken by an analog +// allocation on the given device. +bool iobroker_analog_channel_in_use(const struct device *dev, uint8_t channel); + +// Record an analog claim: the pad is validated (nothing else may hold it) +// and its SoC pad stored alongside the device and channel. Call after the +// implementation has resolved the pad and picked a free channel slot. +// Returns 0, or -EBUSY (pad already claimed), -EINVAL (pin has no SoC pad) +// or -ENOMEM (claim registry full). +int iobroker_analog_claim_add(package_pin_t pin, const struct device *dev, + uint8_t channel); + +// Drop the analog claim for the given device and channel. *soc_pad_out +// receives the claimed pad's SoC pad number for quiescing. Returns true when +// a claim was held. +bool iobroker_analog_claim_remove(const struct device *dev, uint8_t channel, + uint16_t *soc_pad_out); + +// Return a pad to a quiescent state (disconnected from any peripheral +// routing, input buffer and driver off, no pulls). Analog-only pads, which no +// GPIO controller covers, are left alone. +void iobroker_gpio_pad_quiesce(uint16_t soc_pad); diff --git a/ports/zephyr-cp/internal-modules/iobroker/src/nordic/nrf/iobroker_analog.c b/ports/zephyr-cp/internal-modules/iobroker/src/nordic/nrf/iobroker_analog.c new file mode 100644 index 00000000000..5bf0b313e71 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/src/nordic/nrf/iobroker_analog.c @@ -0,0 +1,179 @@ +// nRF analog pad allocation for the iobroker module. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-License-Identifier: MIT + +// The SAADC's analog inputs are hardwired to pads: each pad exposes at most +// one AIN input and there is no runtime routing (unlike the digital +// peripherals' PSEL). Allocate therefore resolves the pad's AIN number from +// the SoC's pin functions table, claims the pad and hands out a free SAADC +// channel slot (the software channels are a runtime resource; Zephyr's ADC +// API has no channel release, so release unconfigures the slot through +// nrfx_saadc_channels_deconfig()). +// +// No DAC peripheral driver exists in Zephyr for nRF SoCs, so +// iobroker_dac_allocate() reports -ENOSYS. + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "iobroker_internal.h" + +LOG_MODULE_DECLARE(iobroker, CONFIG_LOG_DEFAULT_LEVEL); + +#if !DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_saadc) +// SAADC not enabled in the devicetree: the symbols must still exist, but +// every allocate reports -ENOSYS. +#define ANALOG_INPUT_PADS NULL +#define ANALOG_INPUT_COUNT 0 +static const struct device *analog_input_device(void) { + return NULL; +} +static uint8_t analog_input_channel_count(void) { + return 0; +} +#else + +// Package pad (the SoC package's ball id number, in row-major ball order) +// that each SAADC analog input is bonded to, in AIN number order. From the +// pin tables of the packages/ transcriptions (the packages' gpio column +// names the GPIO pad each ball id bonds to; e.g. aQFN73 ball id 3 = P0.02 +// = AIN0). SoCs without a transcribed table report -ENOSYS; add the +// mapping through the SoC's package pin map. +#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(NRF52832_XXAA) || \ + defined(NRF52820_XXAA) || defined(NRF52811_XXAA) || defined(NRF52810_XXAA) || \ + defined(NRF52805_XXAA) +// nRF52 series: AIN0..AIN7 are P0.02..P0.05 and P0.28..P0.31, ball ids from +// nrf52840_aqfn73.toml (ball grid packages remap them per package; the +// module maps share the aQFN73 balls). +static const uint16_t analog_input_pads[] = { 3, 16, 30, 32, 15, 2, 14, 1 }; +#define ANALOG_INPUT_PADS analog_input_pads +#define ANALOG_INPUT_COUNT ARRAY_SIZE(analog_input_pads) +#elif defined(NRF5340_XXAA_APPLICATION) +// nRF5340 application core: AIN0..AIN7 are P0.13..P0.20, ball ids from +// nrf5340_qkaa.toml. +static const uint16_t analog_input_pads[] = { 82, 69, 70, 84, 71, 72, 86, 73 }; +#define ANALOG_INPUT_PADS analog_input_pads +#define ANALOG_INPUT_COUNT ARRAY_SIZE(analog_input_pads) +#else +// TODO: add the AIN -> ball id mapping from the SoC's package pin map for +// this SoC. +#define ANALOG_INPUT_PADS NULL +#define ANALOG_INPUT_COUNT 0 +#endif + +static const struct device *analog_input_device(void) { + return DEVICE_DT_GET_ANY(nordic_nrf_saadc); +} + +static uint8_t analog_input_channel_count(void) { + // One software channel slot per hardware SAADC channel. + return SAADC_CH_NUM; +} + +#endif // DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_saadc) + +int iobroker_adc_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + if (ANALOG_INPUT_COUNT == 0) { + LOG_DBG("analog allocate: no AIN pad table for this SoC"); + return -ENOSYS; + } + + uint16_t soc_pad; + int ret = iobroker_package_pin_soc_pad(pin, &soc_pad); + if (ret < 0) { + LOG_WRN("analog allocate: package pin %u not in package map", + (unsigned)pin); + return ret; + } + + // Find the analog input the pad is bonded to. + uint8_t input = 0; + bool found = false; + for (uint8_t i = 0; i < ANALOG_INPUT_COUNT; i++) { + if (ANALOG_INPUT_PADS[i] == soc_pad) { + input = i; + found = true; + break; + } + } + if (!found) { + LOG_WRN("analog allocate: pad %u has no analog input", (unsigned)soc_pad); + return -EINVAL; + } + + const struct device *dev = analog_input_device(); + if (dev == NULL || !device_is_ready(dev)) { + LOG_WRN("analog allocate: no ready SAADC device"); + return -ENODEV; + } + + // Hand out a free channel slot. + uint8_t count = analog_input_channel_count(); + uint8_t channel = count; + for (uint8_t i = 0; i < count; i++) { + if (!iobroker_analog_channel_in_use(dev, i)) { + channel = i; + break; + } + } + if (channel == count) { + LOG_WRN("analog allocate: %s has no free channel", dev->name); + return -ENOMEM; + } + + ret = iobroker_analog_claim_add(pin, dev, channel); + if (ret < 0) { + return ret; + } + + // The pad's digital side is quiesced: no routing, no input buffer, no + // pull. The analog input is an internal mux setting, so nothing else to + // apply until the caller configures the channel. + iobroker_gpio_pad_quiesce(soc_pad); + + *dev_out = dev; + *channel_out = channel; + *input_out = input; + LOG_INF("adc allocate: package pin %u (pad %u) -> %s channel %u, AIN%u", + (unsigned)pin, (unsigned)soc_pad, dev->name, (unsigned)channel, + (unsigned)input); + return 0; +} + +bool iobroker_adc_release(const struct device *dev, uint8_t channel) { + uint16_t soc_pad; + if (!iobroker_analog_claim_remove(dev, channel, &soc_pad)) { + LOG_DBG("adc release: %s channel %u not allocated", + dev == NULL ? "(null)" : dev->name, (unsigned)channel); + return false; + } + + // Unconfigure the channel so the slot becomes usable again; Zephyr's ADC + // API has no channel release, so this goes through the nrfx HAL the + // Zephyr driver is built on (0 on success, negative errno on failure). + int ret = nrfx_saadc_channels_deconfig(BIT(channel)); + if (ret != 0) { + LOG_WRN("adc release: deconfig of %s channel %u failed: %d", + dev->name, (unsigned)channel, ret); + } + + iobroker_gpio_pad_quiesce(soc_pad); + LOG_INF("adc release: %s channel %u released", dev->name, + (unsigned)channel); + return true; +} + +// No DAC driver exists in Zephyr for nRF SoCs, so IOBROKER_DAC is always 0 +// here and the core's -ENOSYS stub covers iobroker_dac_allocate(). diff --git a/ports/zephyr-cp/internal-modules/iobroker/src/nordic/nrf/iobroker_route.c b/ports/zephyr-cp/internal-modules/iobroker/src/nordic/nrf/iobroker_route.c new file mode 100644 index 00000000000..f6ae429c252 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/src/nordic/nrf/iobroker_route.c @@ -0,0 +1,449 @@ +// iobroker: nRF runtime pin routing. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +// / = nordic/nrf: one implementation covers every nRF SoC, +// because their pin control encoding is a simple bit-packed value (see +// zephyr/dt-bindings/pinctrl/nrf-pinctrl.h and soc/nordic/common/pinctrl_soc.h) +// that can be computed at runtime, and any peripheral function can be routed +// to (almost) any pin via PSEL. +// +// Compiled whenever CONFIG_PINCTRL_NRF is on. The allocate/release +// definitions here only exist with the full feature set +// (CONFIG_PINCTRL_DYNAMIC and CONFIG_DEVICE_DEINIT_SUPPORT); otherwise the +// stubs in the core report -ENOSYS. + +#include +#include +#include + +#include +#include +#include +#include + +#include "iobroker_internal.h" + +LOG_MODULE_DECLARE(iobroker, CONFIG_LOG_DEFAULT_LEVEL); + +#if IOBROKER_ROUTING + +// Bit positions/fields replicated from nrf-pinctrl.h so that entries can be +// encoded at runtime instead of by the DT macros. +#define NRF_PSEL_FUN(fun) (((uint32_t)(fun) & NRF_FUN_MSK) << NRF_FUN_POS) +#define NRF_PSEL_PIN(pin) (((uint32_t)(pin) & NRF_PIN_MSK) << NRF_PIN_POS) +#define NRF_PSEL_DISCONNECT(fun) \ + ((uint32_t)NRF_PIN_DISCONNECTED | NRF_PSEL_FUN(fun)) +#define NRF_PSEL_PULL_UP ((uint32_t)NRF_PULL_UP << NRF_PULL_POS) +#define NRF_PSEL_LP ((uint32_t)NRF_LP_ENABLE << NRF_LP_POS) +#define NRF_PSEL_CLOCKPIN BIT(NRF_CLOCKPIN_ENABLE_POS) +// Pin + function bits, used to compare a request against a devicetree state. +#define NRF_PSEL_PINFUN_MASK \ + (((uint32_t)NRF_PIN_MSK << NRF_PIN_POS) | ((uint32_t)NRF_FUN_MSK << NRF_FUN_POS)) + +// Log decode helpers: turn a fun code and a SoC pad into readable names, so +// that the logs say which peripheral function goes to which GPIO. +static const char *nrf_fun_name(uint32_t fun) { + static const char *const names[] = { + "UART_TX", "UART_RX", "UART_RTS", "UART_CTS", + "SPIM_SCK", "SPIM_MOSI", "SPIM_MISO", + "SPIS_SCK", "SPIS_MOSI", "SPIS_MISO", "SPIS_CSN", + "TWIM_SCL", "TWIM_SDA", + }; + if (fun < ARRAY_SIZE(names)) { + return names[fun]; + } + return "(other)"; +} + +// Render a SoC pad as a GPIO name ("P0.09"), or "disconnected" for +// IOBROKER_NO_PIN. Writes into buf; returns buf for chaining. +static const char *nrf_pad_name(uint32_t soc_pad, char *buf, size_t size) { + if (soc_pad == IOBROKER_NO_PIN) { + return "disconnected"; + } + snprintf(buf, size, "P%u.%02u", (unsigned)(soc_pad / 32U), + (unsigned)(soc_pad % 32U)); + return buf; +} + +// Decode one pinctrl entry into flags text, e.g. "pull-up clockpin". buf +// receives an empty string when neither flag is set. +static const char *nrf_pin_flags(uint32_t psel, char *buf, size_t size) { + buf[0] = '\0'; + size_t used = 0; + uint32_t pull = NRF_GET_PULL(psel); + if (pull == NRF_PULL_UP) { + used += (size_t)snprintf(buf + used, size - used, "pull-up"); + } else if (pull == NRF_PULL_DOWN) { + used += (size_t)snprintf(buf + used, size - used, "pull-down"); + } + if (NRF_GET_CLOCKPIN_ENABLE(psel)) { + if (used > 0) { + used += (size_t)snprintf(buf + used, size - used, " "); + } + snprintf(buf + used, size - used, "clockpin"); + } + return buf; +} + +// Encode one nRF pin control entry. soc_pad may be IOBROKER_NO_PIN to +// leave the signal disconnected. pull_up enables the internal pull resistor; +// the caller picks it per bus signal (I2C SDA/SCL and UART RX idle high). +static pinctrl_soc_pin_t nrf_psel_encode(uint32_t fun, uint16_t soc_pad, + bool pull_up) { + uint32_t psel; + + if (soc_pad != IOBROKER_NO_PIN) { + psel = NRF_PSEL_PIN(soc_pad) | NRF_PSEL_FUN(fun); + // On nRF54 the GPIO pin clock must be enabled for signals that drive + // the pad. The pinctrl driver ignores this bit where unsupported + // (nRF52/nRF53). + switch (fun) { + case NRF_FUN_TWIM_SDA: + case NRF_FUN_TWIM_SCL: + case NRF_FUN_SPIM_SCK: + case NRF_FUN_SPIM_MOSI: + case NRF_FUN_UART_TX: + psel |= NRF_PSEL_CLOCKPIN; + break; + default: + break; + } + } else { + psel = NRF_PSEL_DISCONNECT(fun); + } + + if (pull_up) { + psel |= NRF_PSEL_PULL_UP; + } + + return psel; +} + +// Check whether a set of requested entries matches the devicetree default +// state of an instance (pin + function only; configuration bits ignored). +static bool nrf_psels_match(const pinctrl_soc_pin_t *psels, uint8_t count, + const pinctrl_soc_pin_t *requested, uint8_t requested_count) { + uint32_t mask = NRF_PSEL_PINFUN_MASK; + + for (uint8_t i = 0; i < requested_count; i++) { + bool found = false; + for (uint8_t j = 0; j < count; j++) { + if ((psels[j] & mask) == (requested[i] & mask)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + // The DT state must not use extra pins either. + for (uint8_t j = 0; j < count; j++) { + bool found = false; + for (uint8_t i = 0; i < requested_count; i++) { + if ((psels[j] & mask) == (requested[i] & mask)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +// Re-point an instance at the requested pins: ensure the device is +// de-initialized (initialization is the caller's job), then swap its pinctrl +// states for entries built at runtime. The caller initializes the device +// afterwards, which applies the new "default" state. +static int iobroker_route(const iobroker_instance_t *inst, iobroker_state_t *state, + const pinctrl_soc_pin_t *pins, uint8_t pin_count) { + if (pin_count > IOBROKER_MAX_PINS) { + return -EINVAL; + } + + LOG_INF("routing %s: de-initializing then swapping in %u runtime psels", + inst->dev->name, (unsigned)pin_count); + int ret = device_deinit(inst->dev); + if (ret < 0 && ret != -ENOSYS && ret != -EPERM) { + LOG_WRN("routing %s: device_deinit failed: %d", inst->dev->name, ret); + return ret; + } + + // The Zephyr pinctrl core requires the same set of state ids to be + // provided, so mirror the ids the device currently has. + uint8_t state_cnt = MIN(inst->pcfg->state_cnt, ARRAY_SIZE(state->states)); + if (state_cnt == 0) { + return -ENOENT; + } + + for (uint8_t i = 0; i < pin_count; i++) { + state->default_pins[i] = pins[i]; + // Sleep state: same pins, low power (input buffer disconnected). + state->sleep_pins[i] = pins[i] | NRF_PSEL_LP; + } + for (uint8_t s = 0; s < state_cnt; s++) { + uint8_t id = inst->pcfg->states[s].id; + state->states[s].id = id; + if (id == PINCTRL_STATE_SLEEP) { + state->states[s].pins = state->sleep_pins; + } else { + state->states[s].pins = state->default_pins; + } + state->states[s].pin_cnt = pin_count; + } + + ret = pinctrl_update_states(inst->pcfg, state->states, state_cnt); + if (ret < 0) { + LOG_WRN("routing %s: pinctrl_update_states failed: %d", + inst->dev->name, ret); + } else { + for (uint8_t i = 0; i < pin_count; i++) { + uint32_t psel = pins[i]; + char pad[12]; + char flags[24]; + LOG_INF("routing %s: psel[%u] = 0x%08x -> %s %s %s", + inst->dev->name, (unsigned)i, (unsigned)psel, + nrf_fun_name(NRF_GET_FUN(psel)), + nrf_pad_name(NRF_GET_PIN(psel), pad, sizeof(pad)), + nrf_pin_flags(psel, flags, sizeof(flags))); + } + } + return ret; +} + +// Allocate an instance from a pool. Instances that the board enabled with +// disconnected pins are routed dynamically to the requested pins. Instances +// with fixed devicetree pins are only used when the request matches their +// existing state exactly. The requested pins are recorded in the instance's +// state so that iobroker_pin_in_use() can report them. +static int iobroker_allocate(const char *kind, const iobroker_instance_t *buses, + size_t count, iobroker_state_t *states, const package_pin_t *requested, + const pinctrl_soc_pin_t *pins, uint8_t pin_count, const struct device **dev_out) { + for (size_t i = 0; i < count; i++) { + iobroker_state_t *state = &states[i]; + if (state->in_use) { + continue; + } + + if (buses[i].dt_psels == NULL) { + int ret = iobroker_route(&buses[i], state, pins, pin_count); + if (ret < 0) { + continue; + } + state->in_use = true; + state->routed = true; + state->pin_count = pin_count; + for (uint8_t j = 0; j < pin_count; j++) { + state->pins[j] = requested[j]; + } + *dev_out = buses[i].dev; + LOG_INF("%s: allocated %s, routed to %u package pins", + kind, buses[i].dev->name, (unsigned)pin_count); + return 0; + } + + if (nrf_psels_match(buses[i].dt_psels, buses[i].dt_psel_count, pins, pin_count)) { + // Already routed to these pins by the devicetree; just claim it. + state->in_use = true; + state->routed = false; + state->pin_count = pin_count; + for (uint8_t j = 0; j < pin_count; j++) { + state->pins[j] = requested[j]; + } + *dev_out = buses[i].dev; + LOG_INF("%s: allocated %s, devicetree pins match request", + kind, buses[i].dev->name); + return 0; + } + } + LOG_WRN("%s: no free instance for the requested pins", kind); + return -ENODEV; +} + +// Returns -EBUSY when any requested pin is already claimed by an allocated +// instance, so that a pin is only ever used by one allocate() call at a time. +static int iobroker_check_request(const char *kind, const package_pin_t *pins, + size_t count) { + for (size_t i = 0; i < count; i++) { + if (iobroker_pin_in_use(pins[i])) { + LOG_WRN("%s: package pin %u already in use", kind, + (unsigned)pins[i]); + return -EBUSY; + } + } + return 0; +} + +static bool iobroker_state_find(const struct device *dev, iobroker_state_t **state_out) { + for (size_t i = 0; i < iobroker_i2c_bus_count; i++) { + if (iobroker_i2c_buses[i].dev == dev) { + *state_out = &iobroker_i2c_bus_states[i]; + return true; + } + } + for (size_t i = 0; i < iobroker_spi_bus_count; i++) { + if (iobroker_spi_buses[i].dev == dev) { + *state_out = &iobroker_spi_bus_states[i]; + return true; + } + } + for (size_t i = 0; i < iobroker_uart_bus_count; i++) { + if (iobroker_uart_buses[i].dev == dev) { + *state_out = &iobroker_uart_bus_states[i]; + return true; + } + } + return false; +} + +bool iobroker_release(const struct device *dev) { + iobroker_state_t *state = NULL; + if (!iobroker_state_find(dev, &state)) { + LOG_WRN("release: %s is not an iobroker-managed instance", + dev == NULL ? "(null)" : dev->name); + return false; + } + bool routed = state->routed; + // De-init so that the device ends up de-initialized (like deferred-init + // devices are after boot); the caller initializes it again when it + // allocates the instance next. + (void)device_deinit(dev); + state->in_use = false; + state->routed = false; + state->pin_count = 0; + LOG_INF("released %s (was dynamically routed: %u)", dev->name, + (unsigned)routed); + return routed; +} + +int iobroker_i2c_allocate(package_pin_t sda, package_pin_t scl, + const struct device **dev_out) { + LOG_INF("i2c allocate: sda=%u scl=%u", (unsigned)sda, (unsigned)scl); + const package_pin_t requested[] = { sda, scl }; + int ret = iobroker_check_request("i2c", requested, 2); + if (ret < 0) { + return ret; + } + uint16_t sda_pad; + uint16_t scl_pad; + uint16_t sda_gpio; + uint16_t scl_gpio; + if (iobroker_package_pin_soc_pad(sda, &sda_pad) < 0 || + iobroker_package_pin_soc_pad(scl, &scl_pad) < 0 || + iobroker_pad_gpio(sda_pad, &sda_gpio) < 0 || + iobroker_pad_gpio(scl_pad, &scl_gpio) < 0) { + LOG_WRN("i2c allocate: package pin %u or %u is unknown or has no GPIO", + (unsigned)sda, (unsigned)scl); + return -EINVAL; + } + char sda_name[12]; + char scl_name[12]; + LOG_INF("i2c allocate: SDA package pin %u -> %s, SCL package pin %u -> %s", + (unsigned)sda, nrf_pad_name(sda_gpio, sda_name, sizeof(sda_name)), + (unsigned)scl, nrf_pad_name(scl_gpio, scl_name, sizeof(scl_name))); + pinctrl_soc_pin_t pins[2]; + // Open-drain bus: both lines idle high via the internal pull-up. + pins[0] = nrf_psel_encode(NRF_FUN_TWIM_SDA, sda_gpio, true); + pins[1] = nrf_psel_encode(NRF_FUN_TWIM_SCL, scl_gpio, true); + return iobroker_allocate("i2c", iobroker_i2c_buses, iobroker_i2c_bus_count, + iobroker_i2c_bus_states, requested, pins, 2, dev_out); +} + +int iobroker_spi_allocate(package_pin_t clock, package_pin_t mosi, + package_pin_t miso, const struct device **dev_out) { + LOG_INF("spi allocate: clock=%u mosi=%u miso=%u", (unsigned)clock, + (unsigned)mosi, (unsigned)miso); + const package_pin_t requested[] = { clock, mosi, miso }; + int ret = iobroker_check_request("spi", requested, 3); + if (ret < 0) { + return ret; + } + uint16_t clock_pad; + uint16_t mosi_pad; + uint16_t miso_pad; + uint16_t clock_gpio; + uint16_t mosi_gpio; + uint16_t miso_gpio; + if (iobroker_package_pin_soc_pad(clock, &clock_pad) < 0 || + iobroker_package_pin_soc_pad(mosi, &mosi_pad) < 0 || + iobroker_package_pin_soc_pad(miso, &miso_pad) < 0 || + iobroker_pad_gpio(clock_pad, &clock_gpio) < 0 || + iobroker_pad_gpio(mosi_pad, &mosi_gpio) < 0 || + iobroker_pad_gpio(miso_pad, &miso_gpio) < 0) { + LOG_WRN("spi allocate: a package pin (%u/%u/%u) is unknown or has no GPIO", + (unsigned)clock, (unsigned)mosi, (unsigned)miso); + return -EINVAL; + } + char clock_name[12]; + char mosi_name[12]; + char miso_name[12]; + LOG_INF("spi allocate: SCK package pin %u -> %s, MOSI %u -> %s, MISO %u -> %s", + (unsigned)clock, nrf_pad_name(clock_gpio, clock_name, sizeof(clock_name)), + (unsigned)mosi, nrf_pad_name(mosi_gpio, mosi_name, sizeof(mosi_name)), + (unsigned)miso, nrf_pad_name(miso_gpio, miso_name, sizeof(miso_name))); + pinctrl_soc_pin_t pins[3]; + // All signals are push-pull outputs (MISO from the peripheral's view). + pins[0] = nrf_psel_encode(NRF_FUN_SPIM_SCK, clock_gpio, false); + pins[1] = nrf_psel_encode(NRF_FUN_SPIM_MOSI, mosi_gpio, false); + pins[2] = nrf_psel_encode(NRF_FUN_SPIM_MISO, miso_gpio, false); + return iobroker_allocate("spi", iobroker_spi_buses, iobroker_spi_bus_count, + iobroker_spi_bus_states, requested, pins, 3, dev_out); +} + +int iobroker_uart_allocate(package_pin_t tx, package_pin_t rx, + package_pin_t rts, package_pin_t cts, const struct device **dev_out) { + LOG_INF("uart allocate: tx=%u rx=%u rts=%u cts=%u", (unsigned)tx, + (unsigned)rx, (unsigned)rts, (unsigned)cts); + const package_pin_t requested[] = { tx, rx, rts, cts }; + int ret = iobroker_check_request("uart", requested, 4); + if (ret < 0) { + return ret; + } + uint16_t tx_pad; + uint16_t rx_pad; + uint16_t rts_pad; + uint16_t cts_pad; + uint16_t tx_gpio; + uint16_t rx_gpio; + uint16_t rts_gpio; + uint16_t cts_gpio; + if (iobroker_package_pin_soc_pad(tx, &tx_pad) < 0 || + iobroker_package_pin_soc_pad(rx, &rx_pad) < 0 || + iobroker_package_pin_soc_pad(rts, &rts_pad) < 0 || + iobroker_package_pin_soc_pad(cts, &cts_pad) < 0 || + iobroker_pad_gpio(tx_pad, &tx_gpio) < 0 || + iobroker_pad_gpio(rx_pad, &rx_gpio) < 0 || + iobroker_pad_gpio(rts_pad, &rts_gpio) < 0 || + iobroker_pad_gpio(cts_pad, &cts_gpio) < 0) { + LOG_WRN("uart allocate: a package pin (%u/%u/%u/%u) is unknown or has no GPIO", + (unsigned)tx, (unsigned)rx, (unsigned)rts, (unsigned)cts); + return -EINVAL; + } + char tx_name[12]; + char rx_name[12]; + char rts_name[12]; + char cts_name[12]; + LOG_INF("uart allocate: TX package pin %u -> %s, RX %u -> %s, RTS %u -> %s, CTS %u -> %s", + (unsigned)tx, nrf_pad_name(tx_gpio, tx_name, sizeof(tx_name)), + (unsigned)rx, nrf_pad_name(rx_gpio, rx_name, sizeof(rx_name)), + (unsigned)rts, nrf_pad_name(rts_gpio, rts_name, sizeof(rts_name)), + (unsigned)cts, nrf_pad_name(cts_gpio, cts_name, sizeof(cts_name))); + pinctrl_soc_pin_t pins[4]; + // RX floats until the peer drives it, so pull it up internally. + pins[0] = nrf_psel_encode(NRF_FUN_UART_TX, tx_gpio, false); + pins[1] = nrf_psel_encode(NRF_FUN_UART_RX, rx_gpio, true); + pins[2] = nrf_psel_encode(NRF_FUN_UART_RTS, rts_gpio, false); + pins[3] = nrf_psel_encode(NRF_FUN_UART_CTS, cts_gpio, false); + return iobroker_allocate("uart", iobroker_uart_buses, iobroker_uart_bus_count, + iobroker_uart_bus_states, requested, pins, 4, dev_out); +} + +#endif // IOBROKER_ROUTING diff --git a/ports/zephyr-cp/internal-modules/iobroker/src/nxp/mcxn/iobroker_analog.c b/ports/zephyr-cp/internal-modules/iobroker/src/nxp/mcxn/iobroker_analog.c new file mode 100644 index 00000000000..a30b78ad360 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/src/nxp/mcxn/iobroker_analog.c @@ -0,0 +1,375 @@ +// NXP MCX N LPADC/LPDAC analog pad allocation for the iobroker module. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-License-Identifier: MIT + +// The LPADC inputs and LPDAC outputs are bonded to pads: each pad exposes at +// most one analog function per unit (a few pads feed both LPADC units and the +// DAC output) and there is no runtime analog routing. Allocate resolves the +// pad's analog function from the SoC's pin functions table and claims the pad. +// +// ADC: allocate additionally hands out a free LPADC command-channel slot (the +// Zephyr channel_id indexes the CMD registers; the number is +// CONFIG_LPADC_CHANNEL_COUNT). The hardware input comes from input_positive, +// which Zephyr encodes as the channel number in bits 0..4 and the B-side mux +// in bit 5 (the driver writes ADCH = input & 0x1f and picks the sample mode's +// side from bit 5). The same channel slot is handed back at release; the +// LPADC driver has no persistent per-channel state (commands are set up per +// adc_read()), so a release only frees the slot. +// +// DAC: the LPDAC has a single output channel, so channel 0 is always the +// slot; values are 12-bit (scaled down by analogout before the write). +// +// Neither needs pin activity: the analog functions are bonded at mux 0 like +// every other analog function on MCX N (see +// modules/hal/nxp/dts/nxp/mcx/MCXN947VDF-pinctrl.h, e.g. ADC0_A2_PIO4_23 and +// DAC0_OUT_PIO4_2), so the quiesced pad exposes the analog signal once the +// driver has configured the peripheral. + +#include +#include +#include + +#include +#include +#include + +#include "iobroker_internal.h" + +LOG_MODULE_DECLARE(iobroker, CONFIG_LOG_DEFAULT_LEVEL); + +#if IOBROKER_DAC && !DT_HAS_COMPAT_STATUS_OKAY(nxp_lpdac) +#error "iobroker: DAC support built but no nxp,lpdac node is okay" +#endif + +#if IOBROKER_ADC + +// Zephyr's io-channel encoding of one LPADC input for the lpadc driver: +// ADCH channel number in the low 5 bits, bit 5 set for a B-side input (the +// driver reads it as input_positive and maps it onto the command's channel +// number and sample-side mode). +#define LPADC_INPUT_A(ch) (ch) +#define LPADC_INPUT_B(ch) (0x20U | (ch)) + +#if defined(CONFIG_SOC_MCXN947) + +// The package pads each LPADC input is bonded to, transcribed from the +// MCXN947 VFBGA184 pin functions (modules/hal/nxp/dts/nxp/mcx/ +// MCXN947VDF-pinctrl.h, entries ADCn_Xy_PIOx_y: unit n, side X (A/B), +// channel y), keyed to the package pad ids the package numbers its pins +// by (mcxn947_vdf.toml: row-major ball order with power balls leaving +// gaps, per the package Pinout workbook; the analog-only ANA_x pads are +// carried by name; e.g. ANA_0 = ball P3 = pad 139, P4_23 = ball U12 = +// pad 181). Entries are ordered by pad; a pad can bond to more than one +// input (across the two units and the mux sides), and the first entry +// wins with the single-ended A-side input preferred. +// +// Add the mapping from the SoC's pin function table for other MCX SoCs; a +// SoC whose table is empty reports -ENOSYS. +struct analog_input { + uint16_t pad; + uint8_t unit; + uint8_t input; +}; + +static const struct analog_input analog_inputs[] = { + // Ball-id pads from the VFBGA184 ball map (mcxn947_vdf.toml): the + // grid's row-major numbering with power balls leaving gaps. The + // analog-only pads carry ANA_x names (ANA_0 = ball P3 = pad 139). + {1, 1, LPADC_INPUT_A(8)}, + {2, 0, LPADC_INPUT_A(23)}, + {3, 0, LPADC_INPUT_A(20)}, + {4, 0, LPADC_INPUT_B(17)}, + {5, 0, LPADC_INPUT_A(13)}, + {6, 0, LPADC_INPUT_A(9)}, + {7, 0, LPADC_INPUT_B(9)}, + {11, 1, LPADC_INPUT_A(9)}, + {12, 0, LPADC_INPUT_A(22)}, + {13, 0, LPADC_INPUT_A(21)}, + {14, 0, LPADC_INPUT_A(19)}, + {15, 0, LPADC_INPUT_B(16)}, + {16, 0, LPADC_INPUT_A(15)}, + {17, 0, LPADC_INPUT_A(14)}, + {18, 0, LPADC_INPUT_A(8)}, + {19, 0, LPADC_INPUT_B(11)}, + {20, 0, LPADC_INPUT_B(10)}, + {25, 1, LPADC_INPUT_A(10)}, + {26, 0, LPADC_INPUT_A(18)}, + {27, 0, LPADC_INPUT_A(17)}, + {28, 0, LPADC_INPUT_A(16)}, + {29, 0, LPADC_INPUT_A(12)}, + {30, 0, LPADC_INPUT_A(11)}, + {31, 0, LPADC_INPUT_A(10)}, + {32, 0, LPADC_INPUT_B(8)}, + {36, 1, LPADC_INPUT_A(13)}, + {37, 1, LPADC_INPUT_A(12)}, + {38, 1, LPADC_INPUT_A(11)}, + {39, 1, LPADC_INPUT_A(14)}, + {41, 0, LPADC_INPUT_B(23)}, + {43, 0, LPADC_INPUT_B(12)}, + {49, 1, LPADC_INPUT_A(15)}, + {51, 0, LPADC_INPUT_B(22)}, + {52, 0, LPADC_INPUT_B(20)}, + {53, 0, LPADC_INPUT_B(19)}, + {54, 0, LPADC_INPUT_B(14)}, + {60, 1, LPADC_INPUT_A(17)}, + {61, 1, LPADC_INPUT_A(16)}, + {62, 0, LPADC_INPUT_B(21)}, + {63, 0, LPADC_INPUT_B(18)}, + {64, 0, LPADC_INPUT_B(13)}, + {70, 1, LPADC_INPUT_A(18)}, + {71, 1, LPADC_INPUT_A(19)}, + {74, 0, LPADC_INPUT_B(15)}, + {99, 1, LPADC_INPUT_A(20)}, + {104, 1, LPADC_INPUT_B(13)}, + {105, 1, LPADC_INPUT_B(14)}, + {110, 1, LPADC_INPUT_A(22)}, + {111, 1, LPADC_INPUT_A(21)}, + {114, 1, LPADC_INPUT_B(15)}, + {115, 1, LPADC_INPUT_B(16)}, + {120, 1, LPADC_INPUT_A(23)}, + {123, 1, LPADC_INPUT_B(10)}, + {124, 1, LPADC_INPUT_B(12)}, + {125, 1, LPADC_INPUT_B(17)}, + {134, 1, LPADC_INPUT_B(11)}, + {139, 0, LPADC_INPUT_A(0)}, + {150, 0, LPADC_INPUT_B(0)}, + {154, 0, LPADC_INPUT_A(6)}, + {155, 0, LPADC_INPUT_B(6)}, + {156, 0, LPADC_INPUT_B(1)}, + {161, 0, LPADC_INPUT_A(4)}, + {161, 1, LPADC_INPUT_A(4)}, + {162, 1, LPADC_INPUT_A(0)}, + {163, 1, LPADC_INPUT_B(0)}, + {165, 0, LPADC_INPUT_A(5)}, + {165, 1, LPADC_INPUT_A(5)}, + {166, 0, LPADC_INPUT_B(5)}, + {166, 1, LPADC_INPUT_B(5)}, + {167, 0, LPADC_INPUT_A(1)}, + {168, 1, LPADC_INPUT_A(6)}, + {169, 1, LPADC_INPUT_B(6)}, + {175, 0, LPADC_INPUT_B(4)}, + {175, 1, LPADC_INPUT_B(4)}, + {181, 0, LPADC_INPUT_A(2)}, + {181, 1, LPADC_INPUT_B(3)}, + {183, 1, LPADC_INPUT_B(8)}, + {184, 1, LPADC_INPUT_B(9)}, +}; +#define ANALOG_INPUT_COUNT ARRAY_SIZE(analog_inputs) + +// The two LPADC units, in devicetree lpadc0/lpadc1 label order; NULL when +// the unit is not enabled or the SoC has no such node. +static const struct device *const analog_adc_devices[2] = { + #if DT_NODE_EXISTS(DT_NODELABEL(lpadc0)) && \ + DT_NODE_HAS_STATUS(DT_NODELABEL(lpadc0), okay) + DEVICE_DT_GET(DT_NODELABEL(lpadc0)), + #else + NULL, + #endif + #if DT_NODE_EXISTS(DT_NODELABEL(lpadc1)) && \ + DT_NODE_HAS_STATUS(DT_NODELABEL(lpadc1), okay) + DEVICE_DT_GET(DT_NODELABEL(lpadc1)), + #else + NULL, + #endif +}; + +static uint8_t analog_adc_channel_count(const struct device *dev) { + (void)dev; + // One command-channel slot per LPADC CMD register. + return CONFIG_LPADC_CHANNEL_COUNT; +} + +#else +// TODO: add the LPADC input -> pad mapping from this SoC's pin function +// table (see CONFIG_SOC_MCXN947 above). +#define ANALOG_INPUT_COUNT 0 + +static const struct device *const analog_adc_devices[2] = {NULL, NULL}; + +static uint8_t analog_adc_channel_count(const struct device *dev) { + (void)dev; + return 0; +} +#endif // CONFIG_SOC_MCXN947 + +int iobroker_adc_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + if (ANALOG_INPUT_COUNT == 0) { + LOG_DBG("adc allocate: no LPADC input table for this SoC"); + return -ENOSYS; + } + + uint16_t soc_pad; + int ret = iobroker_package_pin_soc_pad(pin, &soc_pad); + if (ret < 0) { + LOG_WRN("adc allocate: package pin %u not in package map", + (unsigned)pin); + return ret; + } + + // Find the analog input the pad is bonded to. + const struct analog_input *found = NULL; + for (size_t i = 0; i < ANALOG_INPUT_COUNT; i++) { + if (analog_inputs[i].pad == soc_pad) { + found = &analog_inputs[i]; + break; + } + } + if (found == NULL) { + LOG_WRN("adc allocate: pad %u has no analog input", (unsigned)soc_pad); + return -EINVAL; + } + const struct device *dev = analog_adc_devices[found->unit]; + if (dev == NULL || !device_is_ready(dev)) { + LOG_WRN("adc allocate: no ready LPADC unit %u", (unsigned)found->unit); + return -ENODEV; + } + + // Hand out a free command-channel slot. + uint8_t count = analog_adc_channel_count(dev); + uint8_t channel = count; + for (uint8_t i = 0; i < count; i++) { + if (!iobroker_analog_channel_in_use(dev, i)) { + channel = i; + break; + } + } + if (channel == count) { + LOG_WRN("adc allocate: %s has no free channel", dev->name); + return -ENOMEM; + } + + ret = iobroker_analog_claim_add(pin, dev, channel); + if (ret < 0) { + return ret; + } + + // The pad's digital side is quiesced; the analog input is a bond, not a + // mux setting beyond mux 0, so nothing else to apply until the caller + // configures the channel. + iobroker_gpio_pad_quiesce(soc_pad); + + *dev_out = dev; + *channel_out = channel; + *input_out = found->input; + LOG_INF("adc allocate: package pin %u (pad %u) -> %s channel %u, LPADC%u " + "input 0x%02x", + (unsigned)pin, (unsigned)soc_pad, dev->name, (unsigned)channel, + (unsigned)found->unit, (unsigned)found->input); + return 0; +} + +bool iobroker_adc_release(const struct device *dev, uint8_t channel) { + uint16_t soc_pad; + if (!iobroker_analog_claim_remove(dev, channel, &soc_pad)) { + LOG_DBG("adc release: %s channel %u not allocated", + dev == NULL ? "(null)" : dev->name, (unsigned)channel); + return false; + } + + // The LPADC driver keeps no persistent per-channel configuration: the + // command registers are rewritten by every adc_read() call, so freeing + // the slot needs no device activity. + iobroker_gpio_pad_quiesce(soc_pad); + LOG_INF("adc release: %s channel %u released", dev->name, + (unsigned)channel); + return true; +} + +#endif // IOBROKER_ADC + +#if IOBROKER_DAC + +// Package pad (gpio port index * 32 + pin within the port) bonded to the +// LPDAC outputs (MCXN947 pin functions: DAC0_OUT on P4_02, DAC1_OUT on +// P4_03), as the package map's ball ids (mcxn947_vdf.toml: P4_02 = ball +// T1 = pad 161, P4_03 = ball U1 = pad 175). +#define DAC0_OUTPUT_PAD 161 +#define DAC1_OUTPUT_PAD 175 + +#if DT_NODE_EXISTS(DT_NODELABEL(dac0)) && \ + DT_NODE_HAS_STATUS(DT_NODELABEL(dac0), okay) +#define ANALOG_DAC_DEVICE DEVICE_DT_GET(DT_NODELABEL(dac0)) +#define ANALOG_DAC_OUTPUT_PAD DAC0_OUTPUT_PAD +#elif DT_NODE_EXISTS(DT_NODELABEL(dac1)) && \ + DT_NODE_HAS_STATUS(DT_NODELABEL(dac1), okay) +#define ANALOG_DAC_DEVICE DEVICE_DT_GET(DT_NODELABEL(dac1)) +#define ANALOG_DAC_OUTPUT_PAD DAC1_OUTPUT_PAD +#endif + +#ifdef ANALOG_DAC_DEVICE +int iobroker_dac_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + uint16_t soc_pad; + int ret = iobroker_package_pin_soc_pad(pin, &soc_pad); + if (ret < 0) { + LOG_WRN("dac allocate: package pin %u not in package map", + (unsigned)pin); + return ret; + } + + if (soc_pad != ANALOG_DAC_OUTPUT_PAD) { + LOG_WRN("dac allocate: pad %u is not a DAC output", (unsigned)soc_pad); + return -EINVAL; + } + + const struct device *dev = ANALOG_DAC_DEVICE; + if (dev == NULL || !device_is_ready(dev)) { + LOG_WRN("dac allocate: no ready DAC device"); + return -ENODEV; + } + + // The LPDAC has one output channel. + if (iobroker_analog_channel_in_use(dev, 0)) { + LOG_WRN("dac allocate: %s has no free channel", dev->name); + return -ENOMEM; + } + + ret = iobroker_analog_claim_add(pin, dev, 0); + if (ret < 0) { + return ret; + } + + iobroker_gpio_pad_quiesce(soc_pad); + + *dev_out = dev; + *channel_out = 0; + // The device takes no input selector; the caller ignores this when it + // configures the channel. + *input_out = 0; + LOG_INF("dac allocate: package pin %u (pad %u) -> %s channel 0", + (unsigned)pin, (unsigned)soc_pad, dev->name); + return 0; +} +#else +int iobroker_dac_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + (void)pin; + (void)dev_out; + (void)channel_out; + (void)input_out; + LOG_DBG("dac allocate: no enabled nxp,lpdac instance"); + return -ENOSYS; +} +#endif + +bool iobroker_dac_release(const struct device *dev, uint8_t channel) { + uint16_t soc_pad; + if (!iobroker_analog_claim_remove(dev, channel, &soc_pad)) { + LOG_DBG("dac release: %s channel %u not allocated", + dev == NULL ? "(null)" : dev->name, (unsigned)channel); + return false; + } + + // Zephyr's DAC API has no channel release: the write value stays applied + // until the next channel_setup reconfigures the device. The claim is + // dropped and the pad is quiesced. + iobroker_gpio_pad_quiesce(soc_pad); + LOG_INF("dac release: %s channel %u released", dev->name, + (unsigned)channel); + return true; +} + +#endif // IOBROKER_DAC diff --git a/ports/zephyr-cp/internal-modules/iobroker/src/renesas/ra/iobroker_analog.c b/ports/zephyr-cp/internal-modules/iobroker/src/renesas/ra/iobroker_analog.c new file mode 100644 index 00000000000..ea2e5ffefd3 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/src/renesas/ra/iobroker_analog.c @@ -0,0 +1,127 @@ +// Renesas RA DAC pad allocation for the iobroker module. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-License-Identifier: MIT +// +// The DAC output pads are hardwired: each pad bonds at most one DA output and +// there is no runtime routing (unlike the digital peripherals' PSEL). +// Allocate resolves the pad's DA channel from the SoC's pin functions table +// (the datasheet's "Register settings for input/output pin function" tables, +// e.g. RA8M1/RA8D1 table 19.6: AN007/DA0 = P0.14, AN105/DA1 = P0.15), applies +// the analog switch and claims the pad alongside the channel slot. +// +// The analog switch is the PmnPFS.ASEL bit, applied through the datasheet's +// "Notes on Using Analog Functions" sequence: PMR and PDR 0 so the pad acts +// as a general input, then ASEL 1. The Zephyr pinctrl shim writes the whole +// PmnPFS bundle in one go (with the R_BSP_PinAccessEnable protection wrap), +// so a config of only the ASEL bit implements the sequence. +// +// ADC inputs on RA SoCs are not implemented here yet: resolving a pad to the +// FSP adc_channel_t id and the right unit needs the per-SoC AN0xx/AN1xx +// channel numbering checked against a second datasheet source, so +// iobroker_adc_allocate() stays on the core's -ENOSYS stub for RA. + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "iobroker_internal.h" + +LOG_MODULE_DECLARE(iobroker, CONFIG_LOG_DEFAULT_LEVEL); + +#if IOBROKER_DAC && !DT_HAS_COMPAT_STATUS_OKAY(renesas_ra_dac) +#error "iobroker: DAC support built but no renesas,ra-dac node is okay" +#endif + +#if defined(CONFIG_SOC_SERIES_RA8M1) || defined(CONFIG_SOC_SERIES_RA8D1) +// DAC12 channel 0 is bonded to P0.14 (AN007/DA0 in table 19.6); channel 1 to +// P0.15 (AN105/DA1). Zephyr's renesas_ra_dac driver only supports channel 0: +// both the write and the setup reject any other channel id, so only P0.14 +// resolves. Add the DAx -> pad mapping from the datasheet for new SoCs. +#define RA_DAC_OUTPUT_PAD 14 +#else +// TODO: add the DAx -> pad mapping from the datasheet for this SoC. +#define RA_DAC_OUTPUT_PAD 0xffff +#endif + +// The pinctrl shim holds struct ra_pinctrl_soc_pin { port_num: 4, pin_num: 4, +// cfg } (see zephyr/soc/renesas/ra/common/pinctrl_soc.h); cfg sets only ASEL, +// which is what the analog sequence asks for. +static void ra_analog_enable(uint16_t soc_pad) { + struct ra_pinctrl_soc_pin cfg = { + .port_num = (soc_pad / 32U), + .pin_num = (soc_pad % 32U), + .cfg = (1u << R_PFS_PORT_PIN_PmnPFS_ASEL_Pos), + }; + + pinctrl_configure_pins(&cfg, 1, PINCTRL_REG_NONE); + LOG_DBG("dac allocate: pad %u (port %u pin %u) analog switch applied", + (unsigned)soc_pad, (unsigned)cfg.port_num, (unsigned)cfg.pin_num); +} + +#if IOBROKER_DAC + +int iobroker_dac_allocate(package_pin_t pin, const struct device **dev_out, + uint8_t *channel_out, uint8_t *input_out) { + uint16_t soc_pad; + int ret = iobroker_package_pin_soc_pad(pin, &soc_pad); + if (ret < 0) { + LOG_WRN("dac allocate: package pin %u not in package map", + (unsigned)pin); + return ret; + } + + if (soc_pad != RA_DAC_OUTPUT_PAD) { + LOG_WRN("dac allocate: pad %u is not a DAC output", (unsigned)soc_pad); + return -EINVAL; + } + + const struct device *dev = DEVICE_DT_GET_ANY(renesas_ra_dac); + if (dev == NULL || !device_is_ready(dev)) { + LOG_WRN("dac allocate: no ready DAC device"); + return -ENODEV; + } + + ret = iobroker_analog_claim_add(pin, dev, 0); + if (ret < 0) { + return ret; + } + + ra_analog_enable(soc_pad); + + *dev_out = dev; + *channel_out = 0; + // The device has no selectable inputs; the caller ignores this when it + // configures the channel. + *input_out = 0; + LOG_INF("dac allocate: package pin %u (pad %u) -> %s channel 0", + (unsigned)pin, (unsigned)soc_pad, dev->name); + return 0; +} + +bool iobroker_dac_release(const struct device *dev, uint8_t channel) { + uint16_t soc_pad; + if (!iobroker_analog_claim_remove(dev, channel, &soc_pad)) { + LOG_DBG("dac release: %s channel %u not allocated", + dev == NULL ? "(null)" : dev->name, (unsigned)channel); + return false; + } + + // Zephyr's DAC API has no channel release, and the renesas_ra_dac driver + // only closes the device from the next channel_setup, so there is no + // channel to unconfigure. The claim is dropped and the pad's analog + // switch stays applied until a later GPIO allocation rewires the pad. + LOG_INF("dac release: %s channel %u released", dev->name, + (unsigned)channel); + return true; +} + +#endif // IOBROKER_DAC diff --git a/ports/zephyr-cp/internal-modules/iobroker/tools/gen_package.py b/ports/zephyr-cp/internal-modules/iobroker/tools/gen_package.py new file mode 100644 index 00000000000..0240cc3601c --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/tools/gen_package.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +"""Transcribe a package pin assignment table into a iobroker package TOML. + +Input is a `pdftotext -layout` dump of a Nordic SoC datasheet (the module's +datasheets/ directory keeps the PDFs; run pdftotext -layout yourself). The +script locates one "X.Y.Z () package pin assignments" section, +parses its pin table and cross-checks the GPIO pads it finds against the pads +named in the section's pin assignment figure. + +The pin tables are laid out with the pin number vertically centered in its +row, so a pin's GPIO pad name can appear on the line before its number. The +parser therefore assigns every GPIO pad (P.) to the next pin +number at or after it in reading order; sequential pin numbering makes that +unambiguous. Every mapped pin is printed for manual review -- the datasheet +figure remains the source of truth, so diff the output against it. + +Usage (nRF54 datasheets, " () package pin assignments" with +numeric QFN pin numbers or a ball grid): + + gen_package.py DATASHEET.txt --section 10.1.4 --name nrf54l15_qfn48 \ + --socs SOC_NRF54L15,SOC_NRF54L10,SOC_NRF54L05 --pins 48 \ + --source nRF54L15_nRF54L10_nRF54L05_Datasheet_v1.0.pdf \ + --out ../packages/nrf54l15_qfn48.toml + +Ball grids label their pins "A5"-style instead of numbering them; pass the +grid's row letters with --rows (comma-separated when the grid has two-letter +rows, as the aQFN grids do: --rows A,B,...,Y,AA,...,AL) and --cols. + +nRF52/nRF53 product specifications name their sections " pin/ball +assignments" without a package code and lay the table out the same way (the +"Pin" column holds the ball label, there is no Clock column, and the aQFN +tables follow the main table with a "Corner pads" subsection); the same +options transcribe them: + + gen_package.py DATASHEET.txt --section 9.1.1 --name nrf5340_qkaa \ + --socs SOC_NRF5340_CPUAPP --pins 94 \ + --rows A,B,C,D,E,F,G,H,J,K,L,M,N,P,R,T,U,V,W,Y,AA,AB,AC,AD,AE,AF,AG,AH,AJ,AK,AL \ + --cols 31 --source nRF5340_PS_v1.6.pdf --out ../packages/nrf5340_qkaa.toml +""" + +import argparse +import re +import sys +from pathlib import Path + +PAD_RE = re.compile(r"P(\d+)\.(\d+)") +# Section headings: "10.1.4 QFN48 (QFAA) package pin assignments" (nRF54 +# datasheets; title plus package code) or "9.1.1 aQFN94 pin assignments" / +# "7.1.1 aQFN73 ball assignments" (nRF52840/nRF5340 product specifications; no +# package code). The heading must end in the assignments phrase, which keeps +# table-of-contents lines (trailing dot leaders and page numbers) out. +SECTION_RE = re.compile( + r"^\s*(\d+\.\d+\.\d+)\s+(.+?)\s+((?:package\s+)?(?:pin|ball)\s+assignments)$" +) +# Optional "()" at the end of a section title, as in the nRF54 headings. +TITLE_CODE_RE = re.compile(r"^(.*?)\s*\((\S+)\)$") +FIGURE_END_RE = re.compile(r"^\s*Figure \d+:") +# A pin number line: optionally preceded by the "Yes" clock-pin marker, either +# alone or with the row's first name/function entry on the same line. +PIN_LINE_RE = re.compile(r"^\s*(?:Yes\s+)?(\d+)(?:\s+(\S.*))?$") +# A ball name line (aQFN/CSP/BGA packages): row letter(s) + column at column 0. +BALL_LINE_RE = re.compile(r"^([A-Z]+)(\d+)(?:\s+|$)") +TABLE_HEADER_RE = re.compile(r"^\s*Pin\s+(?:Clock\s+)?Name\s+Function\s+Description") +# The pin table ends at its "Table N:" caption, or -- for aQFN packages, +# which list their corner pads right after the main table -- at the +# "Corner pads" heading. Neither belongs to the table. +TABLE_END_RE = re.compile(r"^\s*(?:Table\s+\d+:|Corner pads\b)") + + +def find_section(lines, section): + """Return (title, code, kind, section lines) for a numbered heading. + + code is the package code in the nRF54 "QFN48 (QFAA)" headings, or None + when the heading carries none ("aQFN94 pin assignments"). kind is the + heading's "package pin assignments"/"pin assignments"/"ball assignments" + phrase, used in the output header comment. + """ + starts = [(i, SECTION_RE.match(line)) for i, line in enumerate(lines)] + starts = [(i, m) for i, m in starts if m] + for index, (i, m) in enumerate(starts): + if m.group(1) == section: + end = starts[index + 1][0] if index + 1 < len(starts) else len(lines) + title, code = m.group(2), None + code_match = TITLE_CODE_RE.match(title) + if code_match: + title, code = code_match.group(1), code_match.group(2) + return title, code, m.group(3), lines[i + 1 : end] + raise SystemExit(f"section {section!r} not found") + + +def parse_table(section_lines, rows=None, cols=10): + """Return ({pin_id: [(port, pin), ...]}, ordered pin ids) from the table. + + Numeric packages (QFN) use 1-based pin numbers validated against + sequential numbering. Ball packages pass the grid's row letters (a list, + since aQFN grids have two-letter rows); pins are (row, column) pairs, + ordered row-major. + """ + header = next((i for i, line in enumerate(section_lines) if TABLE_HEADER_RE.match(line)), None) + if header is None: + raise SystemExit("pin table header ('Pin Clock Name Function ...') not found") + body = section_lines[header + 1 :] + # The table ends at its caption line or at the "Corner pads" subsection + # some packages list after it; neither is part of the table. + end = next((i for i, line in enumerate(body) if TABLE_END_RE.match(line)), None) + if end is not None: + body = body[:end] + + # Pass 1: pin lines. Numeric mode validates against sequential numbering + # so page numbers and stray digits cannot be mistaken for pins; ball mode + # validates against the grid instead. + number_lines = [] + if rows: + valid = {(row, column) for row in rows for column in range(1, cols + 1)} + for i, line in enumerate(body): + m = BALL_LINE_RE.match(line) + if m and (m.group(1), int(m.group(2))) in valid: + ball = (m.group(1), int(m.group(2))) + if ball in (b for _, b in number_lines): + raise SystemExit(f"duplicate ball {ball[0]}{ball[1]}") + number_lines.append((i, ball)) + else: + for i, line in enumerate(body): + m = PIN_LINE_RE.match(line) + if m and int(m.group(1)) == len(number_lines) + 1: + number_lines.append((i, int(m.group(1)))) + if not number_lines: + raise SystemExit("no pin numbers found; table layout mismatch?") + + # Pass 2: assign each GPIO pad to the next pin at or after it. The pin + # number is vertically centered in its table row, so the row's GPIO pad + # name can sit on the line before the number. + pins = {pin: [] for _, pin in number_lines} + for i, line in enumerate(body): + for m in PAD_RE.finditer(line): + owner = next((pin for j, pin in number_lines if j >= i), None) + if owner is None: + raise SystemExit(f"GPIO pad {m.group(0)!r} after the last pin") + pins[owner].append((int(m.group(1)), int(m.group(2)))) + order = [pin for _, pin in number_lines] + return pins, order + + +def parse_figure(section_lines): + """GPIO pads named anywhere in the section's pin assignment figure.""" + pads = set() + for line in section_lines: + if FIGURE_END_RE.match(line): + break + pads.update(PAD_RE.finditer(line)) + return {(int(m.group(1)), int(m.group(2))) for m in pads} + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument("datasheet", type=Path, help="pdftotext -layout dump") + parser.add_argument("--section", required=True, help="e.g. 10.1.4") + parser.add_argument("--name", required=True, help="package map name, e.g. nrf54l15_qfn48") + parser.add_argument( + "--socs", required=True, help="comma-separated Kconfig SoC symbols the map applies to" + ) + parser.add_argument( + "--pins", type=int, required=True, help="expected number of package pins (or balls)" + ) + parser.add_argument( + "--rows", + help="ball row letters, e.g. ABCDEFGHJK (comma-separated A,B,...,AA " + "when the grid has two-letter rows), for ball grid packages; " + "pins are numbered sequentially in row-major order", + ) + parser.add_argument("--cols", type=int, default=10, help="ball grid columns (with --rows)") + parser.add_argument( + "--die-pad", + action="store_true", + help="the table lists the exposed die pad as one pin past --pins; " + "it is bonded to VSS and never routable", + ) + parser.add_argument( + "--source", required=True, help="datasheet file name for the header comment" + ) + parser.add_argument("--out", type=Path, required=True, help="output TOML path") + args = parser.parse_args() + + # Ball row letters: comma-separated when the grid has two-letter rows. + rows = None + if args.rows: + rows = args.rows.split(",") if "," in args.rows else list(args.rows) + + lines = args.datasheet.read_text().splitlines() + title, code, kind, section_lines = find_section(lines, args.section) + + pins, order = parse_table(section_lines, rows=rows, cols=args.cols) + expected_max = args.pins + (1 if args.die_pad else 0) + if len(order) != expected_max: + raise SystemExit(f"parsed {len(order)} pins, expected {expected_max}") + if args.die_pad: + die_pad = order[-1] + if pins[die_pad]: + raise SystemExit(f"die pad {die_pad} unexpectedly bonded to GPIO {pins[die_pad]}") + del pins[die_pad] + order = order[:-1] + if not args.rows: + missing = [n for n in range(1, args.pins + 1) if n not in pins] + if missing: + raise SystemExit(f"pin numbers not found: {missing}") + + # Every pin must bond at most one GPIO pad, and the set of bonded pads + # must match the pads the figure shows for this package. + multi = {n: p for n, p in pins.items() if len(p) > 1} + if multi: + raise SystemExit(f"pins with more than one GPIO pad: {multi}") + table_pads = {p[0] for p in pins.values() if p} + figure_pads = parse_figure(section_lines) + if not figure_pads: + # CSP/BGA figures are unlabeled ball grids; there is nothing to + # cross-check against. The pad-count sanity check below still applies. + print("note: figure names no GPIO pads; skipping table/figure cross-check") + elif table_pads != figure_pads: + raise SystemExit( + "pad mismatch table vs figure:\n" + f" only in table: {sorted(table_pads - figure_pads)}\n" + f" only in figure: {sorted(figure_pads - table_pads)}" + ) + + skipped = [pin for pin in order if not pins[pin]] + + def pin_label(pin): + return f"{pin[0]}{pin[1]}" if isinstance(pin, tuple) else str(pin) + + header_notes = [] + if args.rows: + header_notes += [ + "# Ball packages: 'pin' numbers the balls sequentially in row-major", + "# order (the datasheet only labels them, e.g. B2).", + "", + ] + out_lines = [ + f"# Package pin map for {args.name}, transcribed from {args.source},", + f"# section {args.section} ({title}{f', {code}' if code else ''} {kind}),", + "# by tools/gen_package.py. Review against the datasheet figure before", + "# editing by hand; regenerate instead when the datasheet changes.", + "", + *header_notes, + f'name = "{args.name}"', + f"socs = [{', '.join(f'"{s}"' for s in args.socs.split(','))}]", + "", + ] + for number, pin in enumerate(order, start=1): + pad = pins[pin][0] if pins[pin] else None + if not pad: + continue + soc_pad = pad[0] * 32 + pad[1] + out_lines.append("[[pins]]") + out_lines.append(f"pin = {number}") + if isinstance(pin, tuple): + out_lines.append(f'ball = "{pin[0]}{pin[1]}"') + out_lines.append(f"pad = {soc_pad}") + out_lines.append(f'pad_name = "P{pad[0]}.{pad[1]:02d}"') + out_lines.append("") + if skipped: + out_lines.append("# Package pins without a GPIO pad (not routable):") + out_lines.append("# " + ", ".join(pin_label(pin) for pin in skipped)) + out_lines.append("") + + args.out.write_text("\n".join(out_lines)) + + print( + f"{args.out}: {sum(1 for p in pins.values() if p)} GPIO pads " + f"on {len(order)} package pins, {len(skipped)} skipped" + ) + for number, pin in enumerate(order, start=1): + pad = pins[pin][0] if pins[pin] else None + if pad: + print(f" pin {pin_label(pin):>4} -> P{pad[0]}.{pad[1]:02d}") + + +if __name__ == "__main__": + main() diff --git a/ports/zephyr-cp/internal-modules/iobroker/tools/gen_package_c.py b/ports/zephyr-cp/internal-modules/iobroker/tools/gen_package_c.py new file mode 100644 index 00000000000..a67d0b32d36 --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/tools/gen_package_c.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Render a iobroker package TOML as the C pin map translation unit. + +Run at build time by the module's CMakeLists.txt for the package selected via +CONFIG_IOBROKER_PACKAGE_*; the output lands in the build directory, so +nothing generated is committed. The declarations live in iobroker.h. + +Usage: + gen_package_c.py packages/nrf54l15_qfn48.toml package_pins.c +""" + +import sys +import tomllib +from pathlib import Path + + +def main(): + if len(sys.argv) != 3: + raise SystemExit(__doc__) + toml_path, out_path = Path(sys.argv[1]), Path(sys.argv[2]) + with toml_path.open("rb") as f: + data = tomllib.load(f) + + lines = [ + f"// Generated from {toml_path.name} by tools/gen_package_c.py -- do not edit.", + f"// Package pin map '{data['name']}' for {', '.join(data['socs'])}.", + "", + "#include ", + "#include ", + "", + "#include ", + "", + "const iobroker_package_pin_t iobroker_package_pins[] = {", + ] + for entry in data["pins"]: + if "pad_name" not in entry and "pad" not in entry: + # Informational module pins (e.g. a castellation bonded to GND): + # they carry no SoC pad, so they map nothing. + continue + # The SoC pad is the pad id the package numbers its pins by (row + # -major over the ball grid, or the datasheet's pin number); module + # packages remap it per entry and SoC packages default to the pin. + pad = entry.get("pad", entry["pin"]) + # The pad's GPIO attachment, or IOBROKER_PAD_NO_GPIO for analog-only + # pads. + gpio = entry.get("gpio", 0xFFFF) + ball = f" ({entry['ball']})" if "ball" in entry else "" + pad_name = f" {entry['pad_name']}" if "pad_name" in entry else "" + comment = f" //{ball}{pad_name}" + lines.append(f" {{ PACKAGE_PIN({entry['pin']}), {pad}, {gpio} }},{comment}") + lines += [ + "};", + f"const size_t iobroker_package_pin_count = {len(data['pins'])};", + "", + ] + out_path.write_text("\n".join(lines)) + + +if __name__ == "__main__": + main() diff --git a/ports/zephyr-cp/internal-modules/iobroker/zephyr/module.yml b/ports/zephyr-cp/internal-modules/iobroker/zephyr/module.yml new file mode 100644 index 00000000000..6d5d548c16e --- /dev/null +++ b/ports/zephyr-cp/internal-modules/iobroker/zephyr/module.yml @@ -0,0 +1,10 @@ +# Zephyr module descriptor for the iobroker module. +# +# The module is loaded from the CircuitPython tree via ZEPHYR_EXTRA_MODULES in +# ports/zephyr-cp/CMakeLists.txt. To externalize it, move this directory into +# its own repository and either point ZEPHYR_EXTRA_MODULES at the new checkout +# or add it to the west manifest instead. +name: iobroker +build: + cmake: . + kconfig: Kconfig diff --git a/ports/zephyr-cp/prj.conf b/ports/zephyr-cp/prj.conf index 864af4c6112..f3a9da38e96 100644 --- a/ports/zephyr-cp/prj.conf +++ b/ports/zephyr-cp/prj.conf @@ -3,6 +3,11 @@ CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_USE_DT_CODE_PARTITION=y +# The MCUboot integration signs zephyr.hex into zephyr.signed.hex; boards whose +# SoC does not select BUILD_OUTPUT_HEX (e.g. STM32 does) otherwise only emit a +# .bin, and the Makefile's firmware.hex merge then has nothing to sign. +CONFIG_BUILD_OUTPUT_HEX=y + CONFIG_DYNAMIC_INTERRUPTS=y CONFIG_UART_INTERRUPT_DRIVEN=y @@ -36,8 +41,25 @@ CONFIG_LOG_BLOCK_IN_THREAD=y CONFIG_EVENTS=y +# Enable the RTC driver layer so boards with a status-okay `rtc` devicetree +# alias (e.g. native_sim's rtc-emul, STM32, RP2040, SmartBond) get a working +# rtc module via common-hal/rtc. Boards without the alias only pay for the +# tiny RTC core (syscall table + rtc_utils). +CONFIG_RTC=y + CONFIG_SERIAL=y +# Fail quickly on hung I2C devices (probe/scan also uses this timeout) +CONFIG_I2C_TRANSFER_TIMEOUT_MS=50 + +# Zephyr's Nordic SoC defconfig sets UART_USE_RUNTIME_CONFIGURE default n +# (soc/nordic/Kconfig.defconfig) for footprint. Without it the UARTE driver +# does not register api.configure and uart_configure() returns -ENOSYS +# unconditionally, which makes busio.UART fail with "Unsupported UART +# configuration" for every config. Runtime configure is required for the +# iobroker-routed UARTs to get their baudrate/parity/stop bits applied. +CONFIG_UART_USE_RUNTIME_CONFIGURE=y + CONFIG_LOG=y CONFIG_LOG_MAX_LEVEL=2 CONFIG_FRAME_POINTER=n @@ -76,3 +98,14 @@ CONFIG_PSA_WANT_ALG_SHA_256=y CONFIG_NVS=y CONFIG_SETTINGS_NVS=y CONFIG_SETTINGS_NVS_SECTOR_COUNT=256 + +# Zephyr's host reserves a connection object for a connectable advertiser, and +# the controller sizes its RX node pool from this too. With the default of 1, +# user code cannot connect as a central or start a timed scan while the BLE +# workflow advertises. 5 matches nordic. About 2.9 KB of RAM per connection. +CONFIG_BT_MAX_CONN=5 + +# Number of stored bonds. The default of 1 means bonding to a second peer +# evicts the BLE workflow's bond (BT_KEYS_OVERWRITE_OLDEST). 3 matches +# espressif's NimBLE default; nordic keeps dozens. +CONFIG_BT_MAX_PAIRED=3 diff --git a/ports/zephyr-cp/socs/nrf52840.conf b/ports/zephyr-cp/socs/nrf52840.conf index bf70997d83f..734d26db8e9 100644 --- a/ports/zephyr-cp/socs/nrf52840.conf +++ b/ports/zephyr-cp/socs/nrf52840.conf @@ -1,4 +1,3 @@ -CONFIG_NRFX_UARTE0=y -CONFIG_NRFX_UARTE1=y +CONFIG_NRFX_UARTE=y CONFIG_NRFX_POWER=y diff --git a/ports/zephyr-cp/supervisor/adaboot_info.c b/ports/zephyr-cp/supervisor/adaboot_info.c new file mode 100644 index 00000000000..c0459c66521 --- /dev/null +++ b/ports/zephyr-cp/supervisor/adaboot_info.c @@ -0,0 +1,93 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// Adaboot identity for the SMP "bootloader info" command. Zephyr's +// application-side os_mgmt handler reports the hardcoded "MCUboot" and +// derives the boot mode from the application's own Kconfig. This hook +// answers with the fork's name and fills mode/running slot from the BLINFO +// TLVs the bootloader left in the zephyr,bootloader-info shared-data +// retention area (written by boot_write_shared_data() on every boot). + +#include + +#include + +#if defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO_HOOK) && \ + defined(CONFIG_RETENTION_BOOTLOADER_INFO_TYPE_MCUBOOT) + +/* The bootloader-info hook API is part of the MCUmgr/SMP stack: the event's + * data struct embeds a live zcbor response encoder and a zcbor_string query, + * and the hook appends its answers with zcbor put calls. The headers (and + * Zephyr's zcbor include path, which only exists when CONFIG_MCUMGR selects + * ZCBOR) are therefore guarded along with the hook: SMP boards compile it + * in, no-Bluetooth boards like frdm_mcxn947 compile the whole hook out and + * never need the headers. */ +#include +#include +#include +#include +#include + +#include +#include + +// Only answer the queries we know how to fill from the shared data; leave +// everything else for Zephyr's handler (which reports the query error). +#define ADABOOT_INFO_NAME "Adaboot" + +static enum mgmt_cb_return adaboot_info_event(uint32_t event, + enum mgmt_cb_return prev_status, int32_t *rc, + uint16_t *group, bool *abort_more, void *data, + size_t data_size) { + struct os_mgmt_bootloader_info_data *info = data; + struct zcbor_string query = *info->query; + int32_t value; + + if (*info->has_output) { + // Another hook already answered; leave the response alone. + return MGMT_CB_OK; + } + + if (*info->decoded == 0) { + // Empty query: identify the bootloader. + if (zcbor_tstr_put_lit(info->zse, "bootloader") && + zcbor_tstr_put_lit(info->zse, ADABOOT_INFO_NAME)) { + *info->has_output = true; + } + return MGMT_CB_OK; + } + + if (query.len == 4 && memcmp(query.value, "mode", 4) == 0) { + int rc = blinfo_lookup(BLINFO_MODE, (char *)&value, sizeof(value)); + if (rc == sizeof(value) && + zcbor_tstr_put_lit(info->zse, "mode") && + zcbor_int32_put(info->zse, value)) { + *info->has_output = true; + } + } else if (query.len == 4 && memcmp(query.value, "slot", 4) == 0) { + // Single-application layouts never write a running-slot TLV; leave + // the query unanswered so Zephyr reports its usual error. + int rc = blinfo_lookup(BLINFO_RUNNING_SLOT, (char *)&value, sizeof(value)); + if (rc == sizeof(value) && + zcbor_tstr_put_lit(info->zse, "slot") && + zcbor_int32_put(info->zse, value)) { + *info->has_output = true; + } + } + + return MGMT_CB_OK; +} + +static struct mgmt_callback adaboot_info_callback = { + .callback = adaboot_info_event, + .event_id = MGMT_EVT_OP_OS_MGMT_BOOTLOADER_INFO, +}; + +void adaboot_info_init(void) { + mgmt_callback_register(&adaboot_info_callback); +} + +#endif diff --git a/ports/zephyr-cp/supervisor/flash.c b/ports/zephyr-cp/supervisor/flash.c index a13bb3cffcb..5acae94b177 100644 --- a/ports/zephyr-cp/supervisor/flash.c +++ b/ports/zephyr-cp/supervisor/flash.c @@ -20,7 +20,17 @@ #include #include +// The filesystem partition node label comes from the Adaboot fork's layout +// dtsi (and the native simulator layouts). Prefer the explicit fatfs/littlefs +// names, then fall back to the older circuitpy_partition name some boards use, +// and finally to runtime discovery of an unpartitioned flash device. +#if FIXED_PARTITION_EXISTS(littlefs_partition) +#define CIRCUITPY_PARTITION littlefs_partition +#elif FIXED_PARTITION_EXISTS(fatfs_partition) +#define CIRCUITPY_PARTITION fatfs_partition +#elif FIXED_PARTITION_EXISTS(circuitpy_partition) #define CIRCUITPY_PARTITION circuitpy_partition +#endif static const struct flash_area *filesystem_area = NULL; #if !FIXED_PARTITION_EXISTS(CIRCUITPY_PARTITION) @@ -534,8 +544,11 @@ static bool _flash_write_block(const uint8_t *data, uint32_t block) { // Flush the cache if we're moving onto a different page. if (_current_page_address != page_address) { // Check to see if we'd write to an erased block and aren't writing to - // our cache. In that case we can write directly. - if (block_erased(address)) { + // our cache. In that case we can write directly. Do not take this fast + // path when the target block is part of the currently cached page: the + // cache copy would go stale and later reads would return the old data. + if (block_erased(address) && + !(_current_page_address == page_address && (_row_flags[block_index] & ROW_LOADED))) { return write_flash(address, data, FILESYSTEM_BLOCK_SIZE); } if (_current_page_address != NO_PAGE_LOADED) { diff --git a/ports/zephyr-cp/supervisor/port.c b/ports/zephyr-cp/supervisor/port.c index e26f256bb2b..5732ae16aa6 100644 --- a/ports/zephyr-cp/supervisor/port.c +++ b/ports/zephyr-cp/supervisor/port.c @@ -7,12 +7,17 @@ #include "supervisor/port.h" #include "mpconfigboard.h" +#include "adaboot/update_mode.h" #include "supervisor/shared/tick.h" #if CIRCUITPY_AUDIOBUSIO_I2SOUT #include "common-hal/audiobusio/I2SOut.h" #endif +#if CIRCUITPY_RTC +#include "shared-bindings/rtc/__init__.h" +#endif + #include #include @@ -35,6 +40,10 @@ #include #include +#if defined(CONFIG_IMG_MANAGER) +#include +#endif + #if defined(CONFIG_TRACING_PERFETTO) && defined(CONFIG_BOARD_NATIVE_SIM) #include "perfetto_encoder.h" #include @@ -213,6 +222,24 @@ safe_mode_t port_init(void) { // We run CircuitPython at the lowest priority (just higher than idle.) // This allows networking and USB to preempt us. k_thread_priority_set(k_current_get(), CONFIG_NUM_PREEMPT_PRIORITIES - 1); + + #if defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO_HOOK) + // Called from here, not SYS_INIT: this file is an archive member that + // nothing references, so the linker never pulls it in unless another + // always-linked translation unit calls into it. + adaboot_info_init(); + #endif + + #if defined(CONFIG_IMG_MANAGER) + // Confirm the running image so MCUboot treats it as permanent instead of + // reverting to the other slot on the next boot. Reaching here means the + // image booted far enough to run the port, which is the usual recovery + // criterion for a test (TEST_AND_CONFIRM) OTA upgrade. + if (!boot_is_img_confirmed()) { + (void)boot_write_img_confirmed(); + } + #endif + k_timer_init(&tick_timer, _tick_function, NULL); perfetto_emit_circuitpython_tracks(); return SAFE_MODE_NONE; @@ -238,6 +265,10 @@ void reset_port(void) { i2sout_reset(); #endif + #if CIRCUITPY_RTC + rtc_reset(); + #endif + #if defined(CONFIG_ARCH_POSIX) native_sim_reset_port_count++; if (native_sim_port_resets != INT32_MAX && @@ -250,6 +281,11 @@ void reset_port(void) { } void reset_to_bootloader(void) { + // The 1200-bps CDC disconnect (and the supervisor's bootloader reset + // cause) asks for the update mode. On bootloaders that distinguish the + // flavors this is the serial DFU one; on the fork's MCUboot adaboot both + // flavors end up in the same update mode. + adaboot_request_update_mode(false); reset_cpu(); } diff --git a/ports/zephyr-cp/supervisor/usb.c b/ports/zephyr-cp/supervisor/usb.c index 6d3bf3286dd..da22cbb0d35 100644 --- a/ports/zephyr-cp/supervisor/usb.c +++ b/ports/zephyr-cp/supervisor/usb.c @@ -33,6 +33,15 @@ LOG_MODULE_REGISTER(cpusb, CONFIG_LOG_DEFAULT_LEVEL); #define USB_DEVICE DT_NODELABEL(zephyr_udc0) +// usbd_register_class() below names the CDC ACM classes "cdc_acm_", where +// n is the devicetree instance number. A board or bootloader overlay that +// adds its own cdc-acm-uart node renumbers them, so check that instance 0 is +// the console and instance 1 the data port. +BUILD_ASSERT(DT_SAME_NODE(DT_INST(0, zephyr_cdc_acm_uart), DT_NODELABEL(cdc_acm_console)), + "CDC ACM instance 0 must be cdc_acm_console"); +BUILD_ASSERT(DT_SAME_NODE(DT_INST(1, zephyr_cdc_acm_uart), DT_NODELABEL(cdc_acm_data)), + "CDC ACM instance 1 must be cdc_acm_data"); + USBD_DEVICE_DEFINE(main_usbd, DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), USB_VID, USB_PID); @@ -102,8 +111,11 @@ int _zephyr_disk_init(struct disk_info *disk) { return 0; } +// We don't check whether the filesystem is littlefs below because we know these +// are APIs used by USB MSC and we enforce it to be FAT at build time. int _zephyr_disk_status(struct disk_info *disk) { - fs_user_mount_t *root = filesystem_circuitpy(); + supervisor_vfs_t *root_vfs = filesystem_circuitpy(); + fs_user_mount_t *root = root_vfs == NULL ? NULL : &root_vfs->fat; int lun = 0; if (root == NULL) { printk("Status: No media\n"); @@ -114,7 +126,7 @@ int _zephyr_disk_status(struct disk_info *disk) { return DISK_STATUS_WR_PROTECT; } // Lock the blockdev once we say we're writable. - if (!locked[lun] && !blockdev_lock(root)) { + if (!locked[lun] && !blockdev_lock((supervisor_vfs_t *)root)) { printk("Status: Locked\n"); return DISK_STATUS_WR_PROTECT; } @@ -123,7 +135,8 @@ int _zephyr_disk_status(struct disk_info *disk) { } int _zephyr_disk_read(struct disk_info *disk, uint8_t *data_buf, uint32_t start_sector, uint32_t num_sector) { - fs_user_mount_t *root = filesystem_circuitpy(); + supervisor_vfs_t *root_vfs = filesystem_circuitpy(); + fs_user_mount_t *root = root_vfs == NULL ? NULL : &root_vfs->fat; uint32_t disk_block_count; disk_ioctl(root, GET_SECTOR_COUNT, &disk_block_count); @@ -136,7 +149,8 @@ int _zephyr_disk_read(struct disk_info *disk, uint8_t *data_buf, uint32_t start_ } int _zephyr_disk_write(struct disk_info *disk, const uint8_t *data_buf, uint32_t start_sector, uint32_t num_sector) { - fs_user_mount_t *root = filesystem_circuitpy(); + supervisor_vfs_t *root_vfs = filesystem_circuitpy(); + fs_user_mount_t *root = root_vfs == NULL ? NULL : &root_vfs->fat; int lun = 0; autoreload_suspend(AUTORELOAD_SUSPEND_USB); disk_write(root, data_buf, start_sector, num_sector); @@ -169,7 +183,8 @@ int _zephyr_disk_write(struct disk_info *disk, const uint8_t *data_buf, uint32_t int _zephyr_disk_ioctl(struct disk_info *disk, uint8_t cmd, void *buff) { - fs_user_mount_t *root = filesystem_circuitpy(); + supervisor_vfs_t *root_vfs = filesystem_circuitpy(); + fs_user_mount_t *root = root_vfs == NULL ? NULL : &root_vfs->fat; int lun = 0; switch (cmd) { case DISK_IOCTL_GET_SECTOR_COUNT: diff --git a/ports/zephyr-cp/sysbuild.conf b/ports/zephyr-cp/sysbuild.conf index e69de29bb2d..84f34aa4bfb 100644 --- a/ports/zephyr-cp/sysbuild.conf +++ b/ports/zephyr-cp/sysbuild.conf @@ -0,0 +1,2 @@ +# Board-specific MCUboot settings are applied conditionally +# via SB_EXTRA_CONF_FILE in sysbuild/CMakeLists.txt. diff --git a/ports/zephyr-cp/sysbuild/CMakeLists.txt b/ports/zephyr-cp/sysbuild/CMakeLists.txt new file mode 100644 index 00000000000..4df7396f57a --- /dev/null +++ b/ports/zephyr-cp/sysbuild/CMakeLists.txt @@ -0,0 +1,35 @@ +# Resolve board aliases before sysbuild runs, so BOARD is the canonical Zephyr +# board id when we derive board-specific file names from it below. +include(${CMAKE_CURRENT_LIST_DIR}/../boards/board_aliases.cmake) + +# Partitioning and bootloader policy are owned by the mcuboot fork +# (bootloader/mcuboot, see its dts/README.md): as a sysbuild module it applies +# dts//.dtsi to both the application and bootloader images and +# defaults SB_CONFIG_BOOTLOADER_* per board. What is left here is CircuitPython's +# own bootloader UI configuration. +set(cp_boards_dir ${CMAKE_CURRENT_LIST_DIR}/../boards) +string(REGEX REPLACE "[@/].*" "" board_name "${BOARD}") + +# Common MCUboot conf (UF2, double-tap, etc.) plus optional board fragment. +# Unused unless a mcuboot image is built for this board. +set(mcuboot_confs ${CMAKE_CURRENT_LIST_DIR}/mcuboot.conf) +if(EXISTS ${cp_boards_dir}/mcuboot_${board_name}.conf) + list(APPEND mcuboot_confs ${cp_boards_dir}/mcuboot_${board_name}.conf) +endif() +set(mcuboot_EXTRA_CONF_FILE "${mcuboot_confs}" + CACHE INTERNAL "MCUboot conf fragments" +) + +# Escape hatch for board defaults we disagree with, e.g. forcing +# SB_CONFIG_BOOTLOADER_NONE=y on a board whose own Kconfig.sysbuild turns MCUboot +# on. Board Kconfig is parsed before module Kconfig, so the fork's defaults +# cannot express this. +if(EXISTS ${cp_boards_dir}/sysbuild_${board_name}.conf) + set(SB_EXTRA_CONF_FILE ${cp_boards_dir}/sysbuild_${board_name}.conf + CACHE INTERNAL "Board sysbuild override" + ) +endif() + +find_package(Sysbuild REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(sysbuild LANGUAGES) diff --git a/ports/zephyr-cp/sysbuild/mcuboot.conf b/ports/zephyr-cp/sysbuild/mcuboot.conf new file mode 100644 index 00000000000..10f2636c4d9 --- /dev/null +++ b/ports/zephyr-cp/sysbuild/mcuboot.conf @@ -0,0 +1,28 @@ +# Emit the bootloader image as a .hex as well: the port's Makefile merges +# mcuboot/zephyr/zephyr.hex with the signed app hex into firmware.hex, so the +# whole (bootloader + app) image can be programmed with a single file. +CONFIG_BUILD_OUTPUT_HEX=y + +# Bootloader-image settings that apply to every board. Bootloader *policy* +# (UF2 vs UART serial recovery, entrance methods, SPI NOR for slot1's device, +# per-board logging to fit small boot partitions) is owned by the mcuboot +# fork (bootloader/mcuboot): its Kconfig defaults decide UF2 vs serial from +# the devicetree's zephyr_udc0, and its conf//.conf fragments +# (applied to this image by boot/zephyr/sysbuild/CMakeLists.txt) layer board +# opt-ins on top. Assigning those symbols here would force one policy onto +# every board, including the USB-less ones that cannot run UF2. + +# The bootloader yields the CPU between image loads. +CONFIG_MAIN_THREAD_PRIORITY=0 + +# Heap for UF2 block tracking bitmap (sized from flash area at runtime) and +# other bootloader allocations. +CONFIG_HEAP_MEM_POOL_SIZE=4096 + +# Detailed USB and entrance logging for debugging (per-board conf fragments +# turn this off where the boot partition is too small for it). +CONFIG_LOG=y +CONFIG_MCUBOOT_LOG_LEVEL_DBG=y +# CONFIG_USBD_LOG_LEVEL_DBG=y +# CONFIG_UDC_DRIVER_LOG_LEVEL_DBG=y +# CONFIG_USBD_MSC_LOG_LEVEL_DBG=y diff --git a/ports/zephyr-cp/tests/__init__.py b/ports/zephyr-cp/tests/__init__.py index c042d744d7d..78b9a17a676 100644 --- a/ports/zephyr-cp/tests/__init__.py +++ b/ports/zephyr-cp/tests/__init__.py @@ -28,6 +28,14 @@ def close(self): self.stdin.close() self.stdout.close() + def read_some(self, limit=65536): + # read1() hands back whatever is already buffered and does at most one + # read on the pipe, so it blocks only when nothing has arrived yet. + data = self.stdout.read1(limit) + if data == b"": + raise EOFError("stdout closed") + return data + @property def in_waiting(self): if self.stdout is None: @@ -47,13 +55,22 @@ def __init__(self, serial_obj, name="serial"): self._stop = threading.Event() self._lock = threading.Lock() self._cv = threading.Condition(self._lock) + self._read_some = getattr(serial_obj, "read_some", None) self._reader = threading.Thread(target=self._reader_loop, daemon=True) self._reader.start() def _reader_loop(self): while not self._stop.is_set(): try: - read = self.serial.read(1) + if self._read_some is not None: + read = self._read_some() + else: + read = self.serial.read(1) + # in_waiting is a non-blocking check on a real serial port, + # so draining it here is free. + waiting = self.serial.in_waiting + if waiting: + read += self.serial.read(waiting) except Exception: # Serial port closed or device disconnected. break diff --git a/ports/zephyr-cp/tests/bsim/test_bsim_ble_adapter.py b/ports/zephyr-cp/tests/bsim/test_bsim_ble_adapter.py index 7651a0ec144..29092b0641c 100644 --- a/ports/zephyr-cp/tests/bsim/test_bsim_ble_adapter.py +++ b/ports/zephyr-cp/tests/bsim/test_bsim_ble_adapter.py @@ -80,6 +80,7 @@ def test_bsim_adapter_disable_stops_advertising(bsim_phy, circuitpython): # --- Adapter state across soft reload --- BSIM_ENABLE_RELOAD_CODE = """\ +import time import _bleio adapter = _bleio.adapter @@ -88,13 +89,21 @@ def test_bsim_adapter_disable_stops_advertising(bsim_phy, circuitpython): print("advertising", adapter.advertising) print("connected", adapter.connected) print("done") +# The sim exits right after the second run's VM cleanup (port_resets). Give the +# UART time to deliver the lines above to the pty before that happens. +time.sleep(0.5) """ @pytest.mark.port_resets(3) +@pytest.mark.duration(30) @pytest.mark.circuitpy_drive({"code.py": BSIM_ENABLE_RELOAD_CODE}) def test_bsim_adapter_state_after_reload(bsim_phy, circuitpython): - """Adapter state is clean after soft reload.""" + """Adapter state is consistent across a soft reload. + + The BLE workflow is advertising on this device, and `import _bleio` must not + disturb it, so adapter.advertising reads True on both runs (as on nordic). + """ circuitpython.serial.wait_for("done") circuitpython.serial.wait_for("Press any key to enter the REPL") circuitpython.serial.write("\x04") @@ -103,7 +112,7 @@ def test_bsim_adapter_state_after_reload(bsim_phy, circuitpython): output = circuitpython.serial.all_output assert output.count("enabled True") >= 2 - assert output.count("advertising False") >= 2 + assert output.count("advertising True") >= 2 assert output.count("connected False") >= 2 diff --git a/ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py b/ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py index 27bb703f379..d5ff222ff5d 100644 --- a/ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py +++ b/ports/zephyr-cp/tests/bsim/test_bsim_ble_packet_buffer.py @@ -604,7 +604,6 @@ def test_bsim_packet_buffer_write_header(bsim_phy, circuitpython1, circuitpython """ -@pytest.mark.duration(5) @pytest.mark.circuitpy_drive({"code.py": BSIM_PB_LENGTHS_CODE}) def test_bsim_packet_buffer_packet_lengths(bsim_phy, circuitpython): """incoming_packet_length and outgoing_packet_length properties.""" diff --git a/ports/zephyr-cp/tests/bsim/test_bsim_ble_workflow_reload.py b/ports/zephyr-cp/tests/bsim/test_bsim_ble_workflow_reload.py new file mode 100644 index 00000000000..cc56b1108e3 --- /dev/null +++ b/ports/zephyr-cp/tests/bsim/test_bsim_ble_workflow_reload.py @@ -0,0 +1,190 @@ +# SPDX-FileCopyrightText: 2026 Dan Halbert for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""The supervisor BLE workflow connection survives a VM reload (bsim). + +A second CircuitPython device connects to the workflow, pairs, and lists the +root directory. The test then interrupts the workflow device's code.py and +reloads it with Ctrl-D, the same path as typing at the "Press any key" prompt +or saving a file from the web editor. The connection must stay up and file +transfer must still work afterwards. + +In the variant where code.py created a `_bleio.Service`, that service must be +gone from the GATT table after the reload, while the workflow connection still +survives: Zephyr can unregister services individually, so no stack restart is +needed. +""" + +import pytest + +from .conftest import get_library_files + +_ADAFRUIT_BLE = get_library_files("adafruit_ble") +_ADAFRUIT_BLE_FILE_TRANSFER = get_library_files("adafruit_ble_file_transfer") + +# Device 1, plain: idle code.py until the test interrupts it. +WORKFLOW_IDLE_CODE = """\ +import supervisor +import time +print("run", supervisor.runtime.run_reason) +print("workflow ready") +# Idle in short sleeps: on zephyr-cp a Ctrl-C from the UART console is only +# noticed when the current time.sleep() ends, so keep each one brief. +while True: + time.sleep(0.5) +""" + +# Device 1, with a user service: create a Battery Service only on the first +# run, so that after the reload it must have been removed by the supervisor, +# not recreated by code.py. +WORKFLOW_SERVICE_CODE = """\ +import supervisor +import time +import _bleio +print("run", supervisor.runtime.run_reason) +if supervisor.runtime.run_reason == supervisor.RunReason.STARTUP: + service = _bleio.Service(_bleio.UUID(0x180F)) + _bleio.Characteristic.add_to_service( + service, _bleio.UUID(0x2A19), + properties=_bleio.Characteristic.READ, + read_perm=_bleio.Attribute.OPEN, + write_perm=_bleio.Attribute.NO_ACCESS, + max_length=1, fixed_length=True, initial_value=b"\\x64", + ) + print("user service created") +print("workflow ready") +# Idle in short sleeps: on zephyr-cp a Ctrl-C from the UART console is only +# noticed when the current time.sleep() ends, so keep each one brief. +while True: + time.sleep(0.5) +""" + +# Device 2: connect, pair, list files, report whether the Battery Service is +# present, then wait through the reload and do it all again on the same +# connection. +CLIENT_CODE = """\ +import time +import _bleio +from adafruit_ble import BLERadio +from adafruit_ble.advertising.standard import ProvideServicesAdvertisement +from adafruit_ble.uuid import StandardUUID +from adafruit_ble_file_transfer import FileTransferService, FileTransferClient + +BATTERY = _bleio.UUID(0x180F) + +ble = BLERadio() + +# Wait for the workflow device's file transfer server to finish starting up +# before scanning. +time.sleep(5) +print("scan start") +target = None +for adv in ble.start_scan(ProvideServicesAdvertisement, timeout=15, active=True): + if StandardUUID(0xFEBB) in adv.services: + target = adv + print("found workflow") + break +ble.stop_scan() +if target is None: + print("no workflow") + raise SystemExit(1) + +connection = ble.connect(target, timeout=10) +print("connected", connection.connected) +connection.pair() +print("paired", connection.paired) + +def battery_present(): + services = connection._bleio_connection.discover_remote_services() + return any(s.uuid == BATTERY for s in services) + +def list_root(tag): + service = connection[FileTransferService] + client = FileTransferClient(service) + names = [e[0] for e in client.listdir("/")] + print("ft names", tag, names) + print("ft code.py listed", tag, "code.py" in names) + +print("battery before", battery_present()) +list_root("before") +print("client ready for reload") + +# The test reloads the workflow device now. Give it time to come back. +time.sleep(20) +print("still connected", connection.connected) +list_root("after") +print("battery after", battery_present()) +print("client done") +connection.disconnect() +""" + +CLIENT_SETTINGS = "CIRCUITPY_BLE_WORKFLOW = false\n" + +CLIENT_DRIVE = { + "code.py": CLIENT_CODE, + "settings.toml": CLIENT_SETTINGS, + **_ADAFRUIT_BLE_FILE_TRANSFER, + **_ADAFRUIT_BLE, +} + + +def _reload_workflow_and_check(workflow, client): + client.serial.wait_for("client ready for reload", timeout=90) + + # Interrupt code.py, then reload from the "Press any key" prompt. Before the + # fix, the first byte at that prompt restarted the BLE stack and dropped the + # workflow connection. + workflow.serial.write("\x03") + workflow.serial.wait_for("KeyboardInterrupt", timeout=30) + workflow.serial.write("\x04") + workflow.serial.wait_for("RunReason.REPL_RELOAD", timeout=30) + + client.serial.wait_for("client done", timeout=90) + + client_output = client.serial.all_output + assert "paired True" in client_output, f"pairing did not succeed: {client_output}" + assert "ft code.py listed before True" in client_output, ( + f"listdir before reload did not include code.py: {client_output}" + ) + assert "still connected True" in client_output, ( + f"workflow connection did not survive the reload: {client_output}" + ) + assert "ft code.py listed after True" in client_output, ( + f"listdir after reload did not include code.py: {client_output}" + ) + + workflow_output = workflow.serial.all_output + assert "safe mode" not in workflow_output.lower(), ( + f"workflow device entered safe mode: {workflow_output}" + ) + return client_output + + +@pytest.mark.port_resets(3) +@pytest.mark.duration(90) +@pytest.mark.circuitpy_drive({"code.py": WORKFLOW_IDLE_CODE}) +@pytest.mark.circuitpy_drive(CLIENT_DRIVE) +def test_bsim_workflow_reload_keeps_connection(board, bsim_phy, circuitpython1, circuitpython2): + """A Ctrl-D reload with no user services leaves the workflow connection up.""" + client_output = _reload_workflow_and_check(circuitpython1, circuitpython2) + assert "battery before False" in client_output + assert "battery after False" in client_output + + +@pytest.mark.port_resets(3) +@pytest.mark.duration(90) +@pytest.mark.circuitpy_drive({"code.py": WORKFLOW_SERVICE_CODE}) +@pytest.mark.circuitpy_drive(CLIENT_DRIVE) +def test_bsim_workflow_reload_removes_user_service( + board, bsim_phy, circuitpython1, circuitpython2 +): + """A reload removes a user-created service without dropping the connection.""" + workflow = circuitpython1 + client_output = _reload_workflow_and_check(workflow, circuitpython2) + assert "user service created" in workflow.serial.all_output + assert "battery before True" in client_output, ( + f"client did not see the user service before the reload: {client_output}" + ) + assert "battery after False" in client_output, ( + f"user service still present after the reload: {client_output}" + ) diff --git a/ports/zephyr-cp/tests/conftest.py b/ports/zephyr-cp/tests/conftest.py index 6a5f9de60d7..a1edc7406fc 100644 --- a/ports/zephyr-cp/tests/conftest.py +++ b/ports/zephyr-cp/tests/conftest.py @@ -5,6 +5,7 @@ import logging import os +import posixpath import subprocess from pathlib import Path @@ -77,12 +78,48 @@ def pytest_configure(config): "markers", "flash_config(erase_block_size=N, total_size=N): override flash simulator parameters", ) + config.addinivalue_line( + "markers", + "fat_filesystem_only: skip the test on littlefs filesystem boards", + ) + config.addinivalue_line( + "markers", + "native_sim_args(*args): extra command-line args passed to native_sim", + ) ZEPHYR_CP = Path(__file__).parent.parent BUILD_DIR = ZEPHYR_CP / "build-native_native_sim" BINARY = BUILD_DIR / "zephyr-cp/zephyr/zephyr.exe" +# Boards parameterized by the filesystem tests. native_native_sim mounts a +# FAT filesystem; native_native_sim_lfs mounts littlefs. +FILESYSTEM_SIMULATORS = ["native_native_sim", "native_native_sim_lfs"] + +# Boards whose flash image is a FAT drive that can be populated from the host +# with mtools. +FAT_FILESYSTEM_BOARDS = { + "native_native_sim", + "native_native_sim_asan", + "native_nrf5340bsim", + "native_nrf54lm20bsim", +} + +# Boards whose flash image holds a littlefs filesystem. Their images are +# populated from the host with littlefs-python, using geometry that matches +# the board overlay and supervisor/shared/filesystem.c: FILESYSTEM_BLOCK_SIZE +# (512) byte blocks over the filesystem partition, with the partition's last +# erase page left out because the supervisor flash cache uses it as its +# scratch page (see supervisor_flash_get_block_count()). +DEFAULT_ERASE_PAGE_SIZE = 4096 +LITTLEFS_FILESYSTEM_BOARDS = { + "native_native_sim_lfs": { + "partition_size": 2032 * 1024, + "erase_page_size": DEFAULT_ERASE_PAGE_SIZE, + "block_size": 512, + }, +} + def _iter_uart_tx_slices(trace_file: Path) -> list[tuple[int, int, str, str]]: """Return UART TX slices as (timestamp_ns, duration_ns, text, device_name).""" @@ -149,21 +186,87 @@ def log_uart_trace_output(trace_file: Path) -> None: ) -# Native_sim boards each test runs against: the non-asan default and the -# asan-enabled build, so memory errors fail tests. -NATIVE_BOARDS = ["native_native_sim", "native_native_sim_asan"] +# Native_sim boards each test runs against: the FAT default, the littlefs +# build, and the asan-enabled build, so filesystem and memory errors fail +# tests. +NATIVE_BOARDS = [ + "native_native_sim", + "native_native_sim_lfs", + "native_native_sim_asan", +] @pytest.fixture(params=NATIVE_BOARDS) def board(request): - """Parametrized over both native_sim builds (non-asan and asan). + board_marker = request.node.get_closest_marker("circuitpython_board") + if board_marker is not None: + return board_marker.args[0] + # Support indirect parametrization, e.g. + # @pytest.mark.parametrize("board", FILESYSTEM_SIMULATORS, indirect=True) + if hasattr(request, "param"): + return request.param + return "native_native_sim" + + +def _lfs_geometry(board, erase_page_size): + """Return (block_size, block_count) matching what CircuitPython mounts.""" + geometry = LITTLEFS_FILESYSTEM_BOARDS[board] + block_size = geometry["block_size"] + page_size = max(erase_page_size, block_size) + # The last erase page of the partition is the supervisor flash cache's + # scratch page, so littlefs only gets the blocks before it. + last_page_start = ((geometry["partition_size"] - block_size) // page_size) * page_size + return block_size, last_page_start // block_size + + +def _populate_lfs_drive(flash, files, board, erase_page_size): + """Copy files onto a littlefs flash image with littlefs-python.""" + from littlefs import LittleFS + from littlefs.context import UserContextFile + + block_size, block_count = _lfs_geometry(board, erase_page_size) + # The fresh image is all erased bytes, so the initial mount fails and + # LittleFS formats it. This matches what CircuitPython does on a blank + # flash, minus the boot files, which are added below. + fs = LittleFS( + context=UserContextFile(str(flash)), + block_size=block_size, + block_count=block_count, + ) + for name, content in files.items(): + parent = posixpath.dirname(name) + if parent: + fs.makedirs(parent, exist_ok=True) + mode = "wb" if isinstance(content, bytes) else "wt" + with fs.open(f"/{name}", mode) as f: + f.write(content) + fs.context.close() + + +def read_lfs_file_from_flash(flash_file, path, board, erase_page_size): + """Extract a file from the littlefs filesystem in the flash image.""" + from littlefs import LittleFS + from littlefs.context import UserContextFile + + block_size, block_count = _lfs_geometry(board, erase_page_size) + fs = LittleFS( + context=UserContextFile(str(flash_file)), + block_size=block_size, + block_count=block_count, + ) + with fs.open(f"/{path}", "rb") as f: + content = f.read() + fs.context.close() + return content.decode() + - The bsim conftest overrides this fixture with its own bsim boards. - """ - board = request.node.get_closest_marker("circuitpython_board") - if board is not None: - return board.args[0] - return request.param +@pytest.fixture(autouse=True) +def _skip_fat_filesystem_only_tests(request, board): + """Honor the ``fat_filesystem_only`` marker on littlefs boards.""" + if request.node.get_closest_marker("fat_filesystem_only") is None: + return + if board in LITTLEFS_FILESYSTEM_BOARDS: + pytest.skip("test requires the FAT filesystem build") @pytest.fixture @@ -212,6 +315,48 @@ def sim_id(request) -> str: return request.node.nodeid.replace("/", "_") +def _populate_drive(flash, files, board, tmp_path, index, erase_page_size): + """Copy files onto the flash image with the host tool matching the board's + filesystem, if any were requested.""" + if files is None: + return + if board in LITTLEFS_FILESYSTEM_BOARDS: + _populate_lfs_drive(flash, files, board, erase_page_size) + else: + _populate_fat_drive(flash, files, board, tmp_path, index) + + +def _populate_fat_drive(flash, files, board, tmp_path, index): + """Copy files onto a FAT flash image with mtools.""" + if board not in FAT_FILESYSTEM_BOARDS: + pytest.skip( + f"cannot preload files onto {board}: only FAT filesystem images " + "can be built from the host" + ) + subprocess.run(["mformat", "-i", str(flash), "::"], check=True) + tmp_drive = tmp_path / f"drive{index}" + tmp_drive.mkdir(exist_ok=True) + + fat_dirs_created = set() + for name, content in files.items(): + src = tmp_drive / name + src.parent.mkdir(parents=True, exist_ok=True) + if isinstance(content, bytes): + src.write_bytes(content) + else: + src.write_text(content) + # Create parent directories on the FAT image. + fat_dir = Path(name).parent + for fat_part in [*reversed(fat_dir.parents), fat_dir]: + if fat_part == Path("."): + continue + fat_path = "::" + str(fat_part) + if fat_path not in fat_dirs_created: + subprocess.run(["mmd", "-i", str(flash), fat_path], check=True) + fat_dirs_created.add(fat_path) + subprocess.run(["mcopy", "-i", str(flash), str(src), f"::{name}"], check=True) + + @pytest.fixture def circuitpython(request, board, sim_id, native_sim_binary, native_sim_env, tmp_path): """Run CircuitPython with given code string and return PTY output.""" @@ -290,6 +435,11 @@ def circuitpython(request, board, sim_id, native_sim_binary, native_sim_env, tmp flash_total_size = flash_config_marker.kwargs.get("total_size", flash_total_size) flash_erase_block_size = flash_config_marker.kwargs.get("erase_block_size", None) flash_write_block_size = flash_config_marker.kwargs.get("write_block_size", None) + # The littlefs image geometry must match the erase page size the + # simulator will run with, which the flash_config marker can override. + populate_erase_page_size = ( + flash_erase_block_size if flash_erase_block_size is not None else DEFAULT_ERASE_PAGE_SIZE + ) procs = [] for i in range(instance_count): @@ -298,29 +448,7 @@ def circuitpython(request, board, sim_id, native_sim_binary, native_sim_env, tmp files = None if len(drives[i][1].args) == 1: files = drives[i][1].args[0] - if files is not None: - subprocess.run(["mformat", "-i", str(flash), "::"], check=True) - tmp_drive = tmp_path / f"drive{i}" - tmp_drive.mkdir(exist_ok=True) - - fat_dirs_created = set() - for name, content in files.items(): - src = tmp_drive / name - src.parent.mkdir(parents=True, exist_ok=True) - if isinstance(content, bytes): - src.write_bytes(content) - else: - src.write_text(content) - # Create parent directories on the FAT image. - fat_dir = Path(name).parent - for fat_part in [*reversed(fat_dir.parents), fat_dir]: - if fat_part == Path("."): - continue - fat_path = "::" + str(fat_part) - if fat_path not in fat_dirs_created: - subprocess.run(["mmd", "-i", str(flash), fat_path], check=True) - fat_dirs_created.add(fat_path) - subprocess.run(["mcopy", "-i", str(flash), str(src), f"::{name}"], check=True) + _populate_drive(flash, files, board, tmp_path, i, populate_erase_page_size) trace_file = tmp_path / f"trace-{i}.perfetto" @@ -387,6 +515,10 @@ def circuitpython(request, board, sim_id, native_sim_binary, native_sim_env, tmp for device in marker.args: cmd.append(f"--disable-i2c={device}") + marker = request.node.get_closest_marker("native_sim_args") + if marker and len(marker.args) > 0: + cmd.extend(marker.args) + if pixel_format is not None: cmd.append(f"--display_pixel_format={PIXEL_FORMAT_BITMASK[pixel_format]}") diff --git a/ports/zephyr-cp/tests/lvfontio/golden/lvfontio_terminal_320x240.png b/ports/zephyr-cp/tests/lvfontio/golden/lvfontio_terminal_320x240.png new file mode 100644 index 00000000000..3cf0d3d75f2 Binary files /dev/null and b/ports/zephyr-cp/tests/lvfontio/golden/lvfontio_terminal_320x240.png differ diff --git a/ports/zephyr-cp/tests/lvfontio/unifont-16.0.02-ja.bin b/ports/zephyr-cp/tests/lvfontio/unifont-16.0.02-ja.bin new file mode 100644 index 00000000000..a219b2d7312 Binary files /dev/null and b/ports/zephyr-cp/tests/lvfontio/unifont-16.0.02-ja.bin differ diff --git a/ports/zephyr-cp/tests/test_analogio.py b/ports/zephyr-cp/tests/test_analogio.py new file mode 100644 index 00000000000..5a249f118cd --- /dev/null +++ b/ports/zephyr-cp/tests/test_analogio.py @@ -0,0 +1,230 @@ +# SPDX-FileCopyrightText: 2026 Scott Shawcroft for Adafruit Industries LLC +# SPDX-License-Identifier: MIT + +"""Test analogio functionality on native_sim.""" + +import re + +import pytest +from perfetto.trace_processor import TraceProcessor + + +ANALOGIO_TRACE_CODE = """\ +import time +import analogio +import microcontroller + +adc0 = analogio.AnalogIn(microcontroller.pin.P_00) +adc1 = analogio.AnalogIn(microcontroller.pin.P_01) +adc2 = analogio.AnalogIn(microcontroller.pin.P_02) + +print(f"t0 {adc0.value} {adc1.value} {adc2.value}") + +time.sleep(2.2) +print(f"t1 {adc0.value} {adc1.value} {adc2.value}") + +time.sleep(0.5) +print(f"t2 {adc0.value} {adc1.value} {adc2.value}") + +time.sleep(0.6) +print(f"t3 {adc0.value} {adc1.value} {adc2.value}") + +time.sleep(1.0) +print(f"t4 {adc0.value} {adc1.value} {adc2.value}") +print("done") +""" + + +ANALOGIO_INPUT_TRACE = { + # Counter tracks carry millivolts, the same unit the Zephyr ADC emul's + # value functions use. Trace timestamps are absolute simulated time; the + # simulator boots at ~2s, and code.py reads at ~2.0s (t0), ~4.2s (t1), + # ~4.7s (t2), ~5.3s (t3) and ~6.3s (t4). The step lands before t3 so all + # channels read full scale from t3 on. AnalogIn reports 16-bit codes + # relative to the 3300 mV internal reference. + "adc.0": [ + (3_000_000_000, 660), # 1/5 of full scale + (5_000_000_000, 3300), + ], + "adc.1": [ + (3_500_000_000, 1320), # 2/5 + (5_000_000_000, 3300), + ], + "adc.2": [ + (4_000_000_000, 1980), # 3/5 + (5_000_000_000, 3300), + ], +} + + +def _code_of_mv(mv: int) -> int: + """Code AnalogIn reports for an input in millivolts: the ADC emul converts + the voltage through the 3300 mV internal reference to the read's bit + depth, truncating like a real converter.""" + return (mv * 65535) // 3300 + + +def _raw14_of_mv(mv: int) -> int: + """Raw 14-bit code the ADC emul reports for an input in millivolts when + --adc-resolution narrows the emulated converter to 14 bits.""" + return (mv * 16383) // 3300 + + +def _mirror_top_bits(raw: int, bits: int) -> int: + """16-bit code a raw *bits*-bit reading is expanded to: the top bits are + replicated into the bottom (atmel-samd style), so zero and full scale are + preserved.""" + shift = 16 - bits + return (raw << shift) | (raw >> (bits - shift)) + + +def _scale_bits(value: int, from_bits: int, to_bits: int) -> int: + from_max = (1 << from_bits) - 1 + to_max = (1 << to_bits) - 1 + return (value * to_max + (from_max // 2)) // from_max + + +def _mv_of_code(code: int, bits: int = 16) -> int: + """Millivolts the DAC track records for a written code at *bits* + resolution: the emulated DAC converts the quantized code through the + 3300 mV reference with the same rounding a converter applies.""" + out_max = (1 << bits) - 1 + return (code * 3300 + out_max // 2) // out_max + + +@pytest.mark.duration(12.0) +@pytest.mark.circuitpy_drive({"code.py": ANALOGIO_TRACE_CODE}) +@pytest.mark.input_trace(ANALOGIO_INPUT_TRACE) +def test_analogio_reads_trace_millivolts(circuitpython): + """ADC values from the trace read as 16-bit codes relative to the reference.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + + t0 = re.search(r"t0 (\d+) (\d+) (\d+)", output) + t1 = re.search(r"t1 (\d+) (\d+) (\d+)", output) + t2 = re.search(r"t2 (\d+) (\d+) (\d+)", output) + t3 = re.search(r"t3 (\d+) (\d+) (\d+)", output) + t4 = re.search(r"t4 (\d+) (\d+) (\d+)", output) + + assert t0 is not None + assert t1 is not None + assert t2 is not None + assert t3 is not None + assert t4 is not None + assert "done" in output + + t0_values = tuple(int(v) for v in t0.groups()) + t1_values = tuple(int(v) for v in t1.groups()) + t2_values = tuple(int(v) for v in t2.groups()) + t3_values = tuple(int(v) for v in t3.groups()) + t4_values = tuple(int(v) for v in t4.groups()) + + expected_mid = (_code_of_mv(660), _code_of_mv(1320), _code_of_mv(1980)) + expected_max = (65535, 65535, 65535) + + assert t0_values == (0, 0, 0) + assert t1_values == expected_mid + assert t2_values == expected_mid + assert t3_values == expected_max + assert t4_values == expected_max + + +@pytest.mark.duration(12.0) +@pytest.mark.native_sim_args("--adc-resolution=14") +@pytest.mark.circuitpy_drive({"code.py": ANALOGIO_TRACE_CODE}) +@pytest.mark.input_trace(ANALOGIO_INPUT_TRACE) +def test_analogio_14bit_reads_mirror_top_bits(circuitpython): + """Reads from a narrowed emulated ADC (like CircuitPython's 14-bit SAADC + hardware reads) stretch to the 16-bit contract by replicating the top + bits into the bottom (atmel-samd style), not by scaling.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + + t1 = re.search(r"t1 (\d+) (\d+) (\d+)", output) + t3 = re.search(r"t3 (\d+) (\d+) (\d+)", output) + assert t1 is not None and t3 is not None + assert "done" in output + + t1_values = tuple(int(v) for v in t1.groups()) + t3_values = tuple(int(v) for v in t3.groups()) + + expected_mid = tuple(_mirror_top_bits(_raw14_of_mv(mv), 14) for mv in (660, 1320, 1980)) + assert t1_values == expected_mid + # The mirroring keeps zero and full scale exact. + assert t3_values == (65535, 65535, 65535) + # And the mirrored codes differ from plain scaling, so the test can tell + # the two apart: 1320 mV reads 6553 *at 14 bits* -> 26213 mirrored instead + # of the scaled 26214. + assert t1_values[1] == 26213 + + +ANALOGOUT_TRACE_CODE = """\ +import time +import analogio +import microcontroller + +out = analogio.AnalogOut(microcontroller.pin.P_00) +out.value = 0 + +time.sleep(0.1) +out.value = 12345 + +time.sleep(0.1) +out.value = 65535 + +print("done") +""" + + +def _parse_counter_track(trace_file, track_name: str) -> list[tuple[int, int]]: + tp = TraceProcessor(file_path=str(trace_file)) + result = tp.query( + f''' + SELECT c.ts, c.value + FROM counter c + JOIN track t ON c.track_id = t.id + WHERE t.name = "{track_name}" + ORDER BY c.ts + ''' + ) + return [(int(row.ts), int(row.value)) for row in result] + + +@pytest.mark.duration(8.0) +@pytest.mark.circuitpy_drive({"code.py": ANALOGOUT_TRACE_CODE}) +def test_analogout_writes_counter_trace(circuitpython): + """Writes appear on the track as millivolts through the 3300 mV reference.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "done" in output + + trace = _parse_counter_track(circuitpython.trace_file, "dac.0") + values = [value for _ts, value in trace] + + half_scale_mv = _mv_of_code(12345) + full_scale_mv = _mv_of_code(65535) + assert full_scale_mv == 3300 + assert int(half_scale_mv) in values + assert int(full_scale_mv) in values + assert values.index(half_scale_mv) < values.index(full_scale_mv) + + +@pytest.mark.duration(8.0) +@pytest.mark.native_sim_args("--dac-resolution=8") +@pytest.mark.circuitpy_drive({"code.py": ANALOGOUT_TRACE_CODE}) +def test_analogout_resolution_override(circuitpython): + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "done" in output + + trace = _parse_counter_track(circuitpython.trace_file, "dac.0") + values = [value for _ts, value in trace] + + # The quantized 8-bit code maps to a different millivolt value than the + # unquantized 16-bit code (621 vs 622), so the override is observable. + assert _mv_of_code(_scale_bits(12345, 16, 8), 8) in values + assert _mv_of_code(_scale_bits(65535, 16, 8), 8) in values diff --git a/ports/zephyr-cp/tests/test_filesystem.py b/ports/zephyr-cp/tests/test_filesystem.py new file mode 100644 index 00000000000..2e149dcfe99 --- /dev/null +++ b/ports/zephyr-cp/tests/test_filesystem.py @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: 2026 Scott Shawcroft for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""Filesystem tests that run against every filesystem the port supports. + +Each test is parameterized over the two native_sim builds so the same +expectations are checked against the FAT filesystem (``native_native_sim``) +and littlefs (``native_native_sim_lfs``). These tests boot from an erased +flash image so they see the filesystem that CircuitPython creates itself, +not one preloaded from the host (see ``conftest`` for that). +""" + +import time + +import pytest + +from .conftest import FILESYSTEM_SIMULATORS + + +def _enter_repl(circuitpython): + """Run the default code.py, then enter the REPL from the reload prompt.""" + circuitpython.serial.wait_for("Press any key to enter the REPL") + circuitpython.serial.write("\r") + circuitpython.serial.wait_for(">>>") + + +def _repl(circuitpython, line): + """Send one line to the REPL and give it time to echo and run.""" + # The native sim console needs CR, not LF, to submit the line. + circuitpython.serial.write(line.rstrip("\n") + "\r") + time.sleep(0.5) + + +@pytest.mark.parametrize("board", FILESYSTEM_SIMULATORS, indirect=True) +@pytest.mark.circuitpy_drive(None) +@pytest.mark.duration(30) +@pytest.mark.port_resets(6) +def test_fresh_filesystem_defaults(circuitpython): + """An erased flash image gets a filesystem with the default files.""" + _enter_repl(circuitpython) + + _repl(circuitpython, "import os\n") + _repl(circuitpython, "entries = os.listdir('/')\n") + _repl(circuitpython, "print('code.py:', 'code.py' in entries)\n") + circuitpython.serial.wait_for("code.py: True") + _repl(circuitpython, "print('lib:', 'lib' in entries)\n") + circuitpython.serial.wait_for("lib: True") + + +@pytest.mark.parametrize("board", FILESYSTEM_SIMULATORS, indirect=True) +@pytest.mark.circuitpy_drive(None) +@pytest.mark.duration(60) +@pytest.mark.port_resets(6) +def test_filesystem_write_read_delete(circuitpython): + """Files survive write, close, reopen and delete.""" + _enter_repl(circuitpython) + + _repl(circuitpython, "with open('/fs_test.txt', 'w') as f:\n") + _repl(circuitpython, " f.write('hello filesystem')\n") + _repl(circuitpython, "\n") + _repl(circuitpython, "print('read:', open('/fs_test.txt').read())\n") + circuitpython.serial.wait_for("read: hello filesystem") + + _repl(circuitpython, "import os\n") + _repl(circuitpython, "print('exists:', 'fs_test.txt' in os.listdir('/'))\n") + circuitpython.serial.wait_for("exists: True") + + _repl(circuitpython, "os.remove('/fs_test.txt')\n") + _repl(circuitpython, "print('exists:', 'fs_test.txt' in os.listdir('/'))\n") + circuitpython.serial.wait_for("exists: False") + + +@pytest.mark.parametrize("board", FILESYSTEM_SIMULATORS, indirect=True) +@pytest.mark.circuitpy_drive(None) +@pytest.mark.duration(60) +@pytest.mark.port_resets(6) +def test_filesystem_mkdir_rename_stat(circuitpython): + """Directories, renames and os.stat work.""" + _enter_repl(circuitpython) + + _repl(circuitpython, "import os\n") + _repl(circuitpython, "os.mkdir('/subdir')\n") + _repl(circuitpython, "with open('/moved.txt', 'w') as f:\n") + _repl(circuitpython, " f.write('moved')\n") + _repl(circuitpython, "\n") + _repl(circuitpython, "os.rename('/moved.txt', '/subdir/renamed.txt')\n") + _repl(circuitpython, "print('dir:', 'subdir' in os.listdir('/'))\n") + circuitpython.serial.wait_for("dir: True") + _repl(circuitpython, "print('renamed:', open('/subdir/renamed.txt').read())\n") + circuitpython.serial.wait_for("renamed: moved") + _repl(circuitpython, "print('stat size:', os.stat('/subdir/renamed.txt')[6])\n") + circuitpython.serial.wait_for("stat size: 5") + + +@pytest.mark.parametrize("board", FILESYSTEM_SIMULATORS, indirect=True) +@pytest.mark.circuitpy_drive(None) +@pytest.mark.duration(60) +@pytest.mark.port_resets(6) +def test_storage_remount(circuitpython): + """storage.remount('/') toggles writability on the root filesystem.""" + _enter_repl(circuitpython) + + _repl(circuitpython, "import storage\n") + _repl(circuitpython, "storage.remount('/', readonly=False)\n") + _repl(circuitpython, "with open('/remount.txt', 'w') as f:\n") + _repl(circuitpython, " f.write('writable')\n") + _repl(circuitpython, "\n") + _repl(circuitpython, "print('read:', open('/remount.txt').read())\n") + circuitpython.serial.wait_for("read: writable") + + _repl(circuitpython, "storage.remount('/', readonly=True)\n") + _repl(circuitpython, "try:\n") + _repl(circuitpython, " with open('/remount2.txt', 'w') as f:\n") + _repl(circuitpython, " f.write('nope')\n") + _repl(circuitpython, " print('unexpected: write succeeded')\n") + _repl(circuitpython, "except OSError as e:\n") + _repl(circuitpython, " print('caught OSError:', e.errno)\n") + _repl(circuitpython, "\n") + circuitpython.serial.wait_for("caught OSError:") + + _repl(circuitpython, "import os\n") + _repl(circuitpython, "print('blocked:', 'remount2.txt' in os.listdir('/'))\n") + circuitpython.serial.wait_for("blocked: False") + + +@pytest.mark.parametrize("board", FILESYSTEM_SIMULATORS, indirect=True) +@pytest.mark.circuitpy_drive(None) +@pytest.mark.duration(120) +@pytest.mark.port_resets(8) +def test_filesystem_persists_across_reset(circuitpython): + """Files written before microcontroller.reset() survive the reboot.""" + _enter_repl(circuitpython) + + _repl(circuitpython, "with open('/persist.txt', 'w') as f:\n") + _repl(circuitpython, " f.write('still here')\n") + _repl(circuitpython, "\n") + _repl(circuitpython, "import microcontroller\n") + _repl(circuitpython, "microcontroller.reset()\n") + + assert circuitpython.reconnect_serial(timeout=30), "simulator did not reboot" + + _enter_repl(circuitpython) + _repl(circuitpython, "print('persisted:', open('/persist.txt').read())\n") + circuitpython.serial.wait_for("persisted: still here") diff --git a/ports/zephyr-cp/tests/test_flash.py b/ports/zephyr-cp/tests/test_flash.py index e2e5f4de14f..9af4c95ab14 100644 --- a/ports/zephyr-cp/tests/test_flash.py +++ b/ports/zephyr-cp/tests/test_flash.py @@ -7,9 +7,17 @@ import pytest +from .conftest import ( + DEFAULT_ERASE_PAGE_SIZE, + LITTLEFS_FILESYSTEM_BOARDS, + read_lfs_file_from_flash, +) -def read_file_from_flash(flash_file, path): - """Extract a file from the FAT filesystem in the flash image.""" + +def read_file_from_flash(flash_file, path, board, erase_page_size=DEFAULT_ERASE_PAGE_SIZE): + """Extract a file from the flash image using the board's filesystem tool.""" + if board in LITTLEFS_FILESYSTEM_BOARDS: + return read_lfs_file_from_flash(flash_file, path, board, erase_page_size) result = subprocess.run( ["mcopy", "-i", str(flash_file), f"::{path}", "-"], capture_output=True, @@ -34,40 +42,44 @@ def read_file_from_flash(flash_file, path): @pytest.mark.circuitpy_drive({"code.py": WRITE_READ_CODE}) -def test_flash_default_erase_size(circuitpython): +def test_flash_default_erase_size(circuitpython, board): """Test filesystem write/read with default 4KB erase blocks.""" circuitpython.wait_until_done() output = circuitpython.serial.all_output assert "content: hello flash" in output assert "done" in output - content = read_file_from_flash(circuitpython.flash_file, "test.txt") + content = read_file_from_flash(circuitpython.flash_file, "test.txt", board) assert content == "hello flash" @pytest.mark.circuitpy_drive({"code.py": WRITE_READ_CODE}) @pytest.mark.flash_config(erase_block_size=65536) -def test_flash_64k_erase_blocks(circuitpython): +def test_flash_64k_erase_blocks(circuitpython, board): """Test filesystem write/read with 64KB erase blocks (128 blocks per page).""" circuitpython.wait_until_done() output = circuitpython.serial.all_output assert "content: hello flash" in output assert "done" in output - content = read_file_from_flash(circuitpython.flash_file, "test.txt") + content = read_file_from_flash( + circuitpython.flash_file, "test.txt", board, erase_page_size=65536 + ) assert content == "hello flash" @pytest.mark.circuitpy_drive({"code.py": WRITE_READ_CODE}) @pytest.mark.flash_config(erase_block_size=262144, total_size=4 * 1024 * 1024) -def test_flash_256k_erase_blocks(circuitpython): +def test_flash_256k_erase_blocks(circuitpython, board): """Test filesystem write/read with 256KB erase blocks (like RA8D1 OSPI).""" circuitpython.wait_until_done() output = circuitpython.serial.all_output assert "content: hello flash" in output assert "done" in output - content = read_file_from_flash(circuitpython.flash_file, "test.txt") + content = read_file_from_flash( + circuitpython.flash_file, "test.txt", board, erase_page_size=262144 + ) assert content == "hello flash" @@ -85,14 +97,19 @@ def test_flash_256k_erase_blocks(circuitpython): @pytest.mark.circuitpy_drive({"code.py": MULTI_FILE_CODE}) @pytest.mark.flash_config(erase_block_size=262144, total_size=4 * 1024 * 1024) -def test_flash_256k_multi_file(circuitpython): +def test_flash_256k_multi_file(circuitpython, board): """Test multiple file writes with 256KB erase blocks to exercise cache flushing.""" circuitpython.wait_until_done() output = circuitpython.serial.all_output assert "multi_file_ok" in output for i in range(5): - content = read_file_from_flash(circuitpython.flash_file, f"file{i}.txt") + content = read_file_from_flash( + circuitpython.flash_file, + f"file{i}.txt", + board, + erase_page_size=262144, + ) assert content == f"data{i}" * 100, f"file{i}.txt mismatch" @@ -109,9 +126,9 @@ def test_flash_256k_multi_file(circuitpython): storage.remount("/", readonly=True) storage.remount("/", readonly=False) -# Now write a small new file. This updates FAT metadata and directory -# entries that share an erase page with original.txt, exercising the -# read-modify-write cycle on the cached page. +# Now write a small new file. This updates filesystem metadata and +# directory entries that share an erase page with original.txt, +# exercising the read-modify-write cycle on the cached page. with open("/small.txt", "w") as f: f.write("tiny") @@ -128,7 +145,7 @@ def test_flash_256k_multi_file(circuitpython): @pytest.mark.circuitpy_drive({"code.py": EXISTING_DATA_CODE}) @pytest.mark.flash_config(erase_block_size=262144, total_size=4 * 1024 * 1024) -def test_flash_256k_existing_data_survives(circuitpython): +def test_flash_256k_existing_data_survives(circuitpython, board): """Test that existing data in an erase page survives when new data is written. With 256KB erase blocks (512 blocks per page), writing to any block in @@ -140,10 +157,14 @@ def test_flash_256k_existing_data_survives(circuitpython): assert "existing_data_ok" in output # Verify both files survived on the actual flash image. - original = read_file_from_flash(circuitpython.flash_file, "original.txt") + original = read_file_from_flash( + circuitpython.flash_file, "original.txt", board, erase_page_size=262144 + ) assert original == "A" * 4000, f"original.txt corrupted: got {len(original)} bytes" - small = read_file_from_flash(circuitpython.flash_file, "small.txt") + small = read_file_from_flash( + circuitpython.flash_file, "small.txt", board, erase_page_size=262144 + ) assert small == "tiny" @@ -161,11 +182,13 @@ def test_flash_256k_existing_data_survives(circuitpython): @pytest.mark.circuitpy_drive({"code.py": OVERWRITE_CODE}) @pytest.mark.flash_config(erase_block_size=262144, total_size=4 * 1024 * 1024) -def test_flash_256k_overwrite(circuitpython): +def test_flash_256k_overwrite(circuitpython, board): """Test overwriting a file with 256KB erase blocks to exercise erase-rewrite cycle.""" circuitpython.wait_until_done() output = circuitpython.serial.all_output assert "overwrite_ok" in output - content = read_file_from_flash(circuitpython.flash_file, "overwrite.txt") + content = read_file_from_flash( + circuitpython.flash_file, "overwrite.txt", board, erase_page_size=262144 + ) assert content == "second version" diff --git a/ports/zephyr-cp/tests/test_lvfontio.py b/ports/zephyr-cp/tests/test_lvfontio.py new file mode 100644 index 00000000000..c79bbfed591 --- /dev/null +++ b/ports/zephyr-cp/tests/test_lvfontio.py @@ -0,0 +1,155 @@ +# SPDX-FileCopyrightText: 2025 Scott Shawcroft for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""lvfontio OnDiskFont tests that run against every filesystem the port supports. + +The font file is preloaded onto the simulated CIRCUITPY drive with the +``circuitpy_drive`` marker, so the same expectations run against FAT +(``native_native_sim``) and littlefs (``native_native_sim_lfs``). This checks +that OnDiskFont's supervisor filesystem API usage works on both. +""" + +import shutil +from pathlib import Path + +import pytest +from PIL import Image + +from .conftest import FILESYSTEM_SIMULATORS + +FONT = (Path(__file__).parent / "lvfontio" / "unifont-16.0.02-ja.bin").read_bytes() + +# あ in UTF-8: full-width (16px advance) glyph found through the fmt 0 (sparse) +# cmap subtable, unlike ASCII which comes from the fmt 2 (range) subtable. + +CODE = """\ +import displayio +import lvfontio +import terminalio + +font = lvfontio.OnDiskFont("/fonts/unifont-ja.bin", max_glyphs=8) +w, h = font.get_bounding_box() +print("bbox:", w, h) +print("bitmap:", font.bitmap.width, font.bitmap.height) + +# Render through terminalio to force glyph caching, which reads the font file +# via the supervisor filesystem API. The kana is full-width and occupies two +# adjacent cache slots. +grid = displayio.TileGrid( + font.bitmap, + pixel_shader=displayio.Palette(2), + width=8, + height=1, + tile_width=w, + tile_height=h, +) +terminal = terminalio.Terminal(grid, font) +terminal.write("AあB") +print("tiles:", [grid[x, 0] for x in range(4)]) + +lit = 0 +for y in range(font.bitmap.height): + for x in range(font.bitmap.width): + if font.bitmap[x, y]: + lit += 1 +print("lit pixels:", lit) + +try: + lvfontio.OnDiskFont("/fonts/missing.fnt") + print("missing: no error") +except ValueError: + print("missing: ValueError") +print("done") +""" + + +@pytest.mark.parametrize("board", FILESYSTEM_SIMULATORS, indirect=True) +@pytest.mark.circuitpy_drive({"code.py": CODE, "fonts/unifont-ja.bin": FONT}) +@pytest.mark.duration(30) +def test_ondiskfont_render(circuitpython): + """OnDiskFont loads, caches glyphs and renders text from the filesystem.""" + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "bbox: 8 16" in output + # The glyph cache is 8 slots of 8px; the font is 16px tall. + assert "bitmap: 64 16" in output + # ASCII 'A' cached in one slot; the full-width kana needed two adjacent + # slots and shifted the next glyph over by two. + assert "tiles: [1, 2, 3, 4]" in output + # Glyph bitmaps were actually read from the font file. + assert "lit pixels: 0" not in output + assert "missing: ValueError" in output + assert "done" in output + + +DISPLAY_CODE = """\ +import board +import displayio +import lvfontio +import terminalio +import time + +font = lvfontio.OnDiskFont("/fonts/unifont-ja.bin", max_glyphs=8) +w, h = font.get_bounding_box() +palette = displayio.Palette(2) +palette[1] = 0xFFFFFF +root = displayio.Group() +grid = displayio.TileGrid( + font.bitmap, + pixel_shader=palette, + width=8, + height=1, + tile_width=w, + tile_height=h, + x=8, + y=24, +) +root.append(grid) +# Replacing the root group hides the console; the frame is then just the +# rendered text, independent of the console boot output. +board.DISPLAY.root_group = root +terminal = terminalio.Terminal(grid, font) +terminal.write("AあB") +print("done") +while True: + time.sleep(1) +""" + + +def _read_image(path): + with Image.open(path) as img: + rgb = img.convert("RGB") + return rgb.width, rgb.height, rgb.tobytes() + + +@pytest.mark.parametrize("board", FILESYSTEM_SIMULATORS, indirect=True) +@pytest.mark.circuitpy_drive({"code.py": DISPLAY_CODE, "fonts/unifont-ja.bin": FONT}) +@pytest.mark.display(capture_times_ns=[4_000_000_000]) +@pytest.mark.duration(8) +def test_ondiskfont_display_golden(request, circuitpython): + """Rendered lvfontio text on the display matches the golden image. + + The code.py replaces the console root group, so the captured frame is + exactly the 'AあB' text rendered from the font file through the + supervisor filesystem API — independent of console output. + """ + circuitpython.wait_until_done() + + output = circuitpython.serial.all_output + assert "done" in output + + captures = circuitpython.display_capture_paths() + if not captures or not captures[0].exists(): + pytest.skip("display capture was not produced") + + golden_path = Path(__file__).parent / "lvfontio" / "golden" / "lvfontio_terminal_320x240.png" + if request.config.getoption("--update-goldens"): + golden_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(captures[0], golden_path) + return + + gw, gh, gpx = _read_image(golden_path) + dw, dh, dpx = _read_image(captures[0]) + assert (dw, dh) == (gw, gh) + assert gpx == dpx, "captured frame does not match the golden image" diff --git a/ports/zephyr-cp/tests/test_rtc.py b/ports/zephyr-cp/tests/test_rtc.py new file mode 100644 index 00000000000..f0d85ae68a7 --- /dev/null +++ b/ports/zephyr-cp/tests/test_rtc.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2026 Scott Shawcroft for Adafruit Industries +# SPDX-License-Identifier: MIT + +"""Test the rtc module against native_sim's emulated RTC. + +On a fresh boot the RTC has never been set (the emulated driver has no +battery backup), so reading it must give the start of the CircuitPython +epoch, 2000-01-01, rather than an error. Setting the datetime must update +what time.localtime() reports, and the RTC must advance on its own. +""" + +import pytest + + +RTC_SET_CODE = """\ +import rtc +import time + +r = rtc.RTC() + +t = time.localtime() +print("UNSET:", t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec) + +r.datetime = time.struct_time((2025, 7, 4, 12, 34, 56, 4, -1, -1)) + +t = time.localtime() +print("SET:", t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec) + +time.sleep(2) +t = time.localtime() +print("ADVANCED:", t.tm_year, (t.tm_hour, t.tm_min, t.tm_sec) != (12, 34, 56)) + +try: + r.calibration = 1 +except NotImplementedError: + print("CALIBRATION NOT SUPPORTED") +""" + + +@pytest.mark.circuitpy_drive({"code.py": RTC_SET_CODE}) +@pytest.mark.duration(30) +def test_rtc_set_and_get(circuitpython): + """Setting rtc.RTC().datetime updates time.localtime() and advances.""" + circuitpython.serial.wait_for("UNSET: 2000 1 1", timeout=30) + circuitpython.serial.wait_for("SET: 2025 7 4 12 34 56", timeout=30) + circuitpython.serial.wait_for("ADVANCED: 2025 True", timeout=30) + circuitpython.serial.wait_for("CALIBRATION NOT SUPPORTED", timeout=30) diff --git a/ports/zephyr-cp/tests/test_storage.py b/ports/zephyr-cp/tests/test_storage.py index 8805a582b6b..002170f3266 100644 --- a/ports/zephyr-cp/tests/test_storage.py +++ b/ports/zephyr-cp/tests/test_storage.py @@ -60,6 +60,7 @@ def test_storage_remount_readonly_blocks_writes(circuitpython): """ +@pytest.mark.fat_filesystem_only @pytest.mark.circuitpy_drive({"code.py": GETMOUNT_CODE}) def test_storage_getmount(circuitpython): """getmount('/') returns the VfsFat object for the root mount.""" @@ -131,6 +132,7 @@ def test_storage_remount_persists_across_reload(circuitpython): """ +@pytest.mark.fat_filesystem_only @pytest.mark.circuitpy_drive({"code.py": LABEL_CODE}) def test_storage_set_label(circuitpython): """VfsFat.label can be set when mounted read-write.""" diff --git a/ports/zephyr-cp/zephyr-config/west.yml b/ports/zephyr-cp/zephyr-config/west.yml index c7c83954cec..0875d8266a3 100644 --- a/ports/zephyr-cp/zephyr-config/west.yml +++ b/ports/zephyr-cp/zephyr-config/west.yml @@ -1,13 +1,63 @@ manifest: group-filter: - +babblesim + # TF-M and friends, no CircuitPython board builds with them. + - -tee projects: - name: nrf_hw_models url: https://github.com/tannewt/ext_nRF_hw_models revision: 41cb69cd08848cc31e91ed62d01d397511981919 path: modules/bsim_hw_models/nrf_hw_models + - name: mcuboot + url: https://github.com/adafruit/adaboot + revision: 14d84b0ba784fec227208d4917b9e7c38e4e87bc + path: bootloader/mcuboot - name: zephyr url: https://github.com/adafruit/zephyr - revision: 52dc937c7cda06a1c18ff6adec281bbeb096b3d4 + revision: 70f1a63cc8bc96ea53ab0c7983f3f8ff60f438bb clone-depth: 100 - import: true + import: + # Skip what no CircuitPython board can use, so west update fetches 37 + # projects instead of 80. Boards come from adafruit, nordic, nxp, + # raspberrypi, renesas, silabs and st; the Pico W's CYW43 is in hal_infineon. + name-blocklist: + # HALs of vendors without a board here + - hal_adi + - hal_afbr + - hal_ambiq + - hal_atmel + - hal_bouffalolab + - hal_espressif + - hal_ethos_u + - hal_gigadevice + - hal_intel + - hal_microchip + - hal_nuvoton + - hal_openisa + - hal_quicklogic + - hal_realtek + - hal_sifli + - hal_tdk + - hal_telink + - hal_ti + - hal_wch + - hal_wurthelektronik + - hal_xtensa + # libraries the port never enables + - acpica + - cmsis-dsp + - cmsis-nn + - dhara + - liblc3 + - libmctp + - libsbc + - lora-basics-modem + - loramac-node + - lvgl + - mipi-sys-t + - mldsa-native + - net-tools + - openthread + - percepio + - uoscore-uedhoc + - zephyr-xenlib diff --git a/py/asmarm.h b/py/asmarm.h index 74e43de7f90..7a68e4c640f 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -50,21 +50,21 @@ #define ASM_ARM_REG_LR (ASM_ARM_REG_R14) #define ASM_ARM_REG_PC (ASM_ARM_REG_R15) -#define ASM_ARM_CC_EQ (0x0 << 28) -#define ASM_ARM_CC_NE (0x1 << 28) -#define ASM_ARM_CC_CS (0x2 << 28) -#define ASM_ARM_CC_CC (0x3 << 28) -#define ASM_ARM_CC_MI (0x4 << 28) -#define ASM_ARM_CC_PL (0x5 << 28) -#define ASM_ARM_CC_VS (0x6 << 28) -#define ASM_ARM_CC_VC (0x7 << 28) -#define ASM_ARM_CC_HI (0x8 << 28) -#define ASM_ARM_CC_LS (0x9 << 28) -#define ASM_ARM_CC_GE (0xa << 28) -#define ASM_ARM_CC_LT (0xb << 28) -#define ASM_ARM_CC_GT (0xc << 28) -#define ASM_ARM_CC_LE (0xd << 28) -#define ASM_ARM_CC_AL (0xe << 28) +#define ASM_ARM_CC_EQ (0x0u << 28) +#define ASM_ARM_CC_NE (0x1u << 28) +#define ASM_ARM_CC_CS (0x2u << 28) +#define ASM_ARM_CC_CC (0x3u << 28) +#define ASM_ARM_CC_MI (0x4u << 28) +#define ASM_ARM_CC_PL (0x5u << 28) +#define ASM_ARM_CC_VS (0x6u << 28) +#define ASM_ARM_CC_VC (0x7u << 28) +#define ASM_ARM_CC_HI (0x8u << 28) +#define ASM_ARM_CC_LS (0x9u << 28) +#define ASM_ARM_CC_GE (0xau << 28) +#define ASM_ARM_CC_LT (0xbu << 28) +#define ASM_ARM_CC_GT (0xcu << 28) +#define ASM_ARM_CC_LE (0xdu << 28) +#define ASM_ARM_CC_AL (0xeu << 28) typedef struct _asm_arm_t { mp_asm_base_t base; diff --git a/py/asmrv32.c b/py/asmrv32.c index e58e42012d9..53481d1d0f3 100644 --- a/py/asmrv32.c +++ b/py/asmrv32.c @@ -515,9 +515,8 @@ void asm_rv32_emit_mov_reg_local_addr(asm_rv32_t *state, mp_uint_t rd, mp_uint_t asm_rv32_opcode_cadd(state, rd, ASM_RV32_REG_SP); } -static const uint8_t RV32_LOAD_OPCODE_TABLE[3] = { - 0x04, 0x05, 0x02 -}; +// ((word: 4) << 8) | ((halfword: 4) << 4) | (byte: 4) +#define RV32_LOAD_OPCODE_FT3(size) ((0x0254 >> (size << 2)) & 0x0F) void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_t rs, int32_t offset, mp_uint_t operation_size) { assert(operation_size <= 2 && "Operation size value out of range."); @@ -532,7 +531,7 @@ void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_ if (MP_FIT_SIGNED(12, scaled_offset)) { // lbu|lhu|lw rd, offset(rs) - asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, RV32_LOAD_OPCODE_TABLE[operation_size], rd, rs, scaled_offset)); + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, RV32_LOAD_OPCODE_FT3(operation_size), rd, rs, scaled_offset)); return; } @@ -545,7 +544,7 @@ void asm_rv32_emit_load_reg_reg_offset(asm_rv32_t *state, mp_uint_t rd, mp_uint_ // lbu|lhu|lw rd, LO(offset)(rd) load_upper_immediate(state, rd, upper); asm_rv32_opcode_cadd(state, rd, rs); - asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, RV32_LOAD_OPCODE_TABLE[operation_size], rd, rd, lower)); + asm_rv32_emit_word_opcode(state, RV32_ENCODE_TYPE_I(0x03, RV32_LOAD_OPCODE_FT3(operation_size), rd, rd, lower)); } void asm_rv32_emit_jump(asm_rv32_t *state, mp_uint_t label) { diff --git a/py/asmrv32.h b/py/asmrv32.h index c25b1aa4e26..f71fe72706a 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -201,6 +201,14 @@ void asm_rv32_end_pass(asm_rv32_t *state); ((op & 0x03) | ((ft6 & 0x3F) << 10) | ((ft2 & 0x03) << 8) | \ ((rlist & 0x0F) << 4) | ((imm & 0x03) << 2)) +#define RV32_ENCODE_TYPE_CMMV(op, ft6, ft2, r1s, r2s) \ + ((op & 0x03) | ((ft6 & 0x3F) << 10) | ((ft2 & 0x03) << 5) | \ + ((((r1s >= ASM_RV32_REG_S0 && r1s <= ASM_RV32_REG_S1) ? \ + (r1s - ASM_RV32_REG_S0) : (r1s - ASM_RV32_REG_S2 + 2)) & 0x07) << 7) | \ + ((((r2s >= ASM_RV32_REG_S0 && r2s <= ASM_RV32_REG_S1) ? \ + (r2s - ASM_RV32_REG_S0) : (r2s - ASM_RV32_REG_S2 + 2)) & 0x07) << 2)) + + #define RV32_ENCODE_TYPE_CR(op, ft4, rs1, rs2) \ ((op & 0x03) | ((rs2 & 0x1F) << 2) | ((rs1 & 0x1F) << 7) | ((ft4 & 0x0F) << 12)) @@ -444,12 +452,36 @@ static inline void asm_rv32_opcode_cxor(asm_rv32_t *state, mp_uint_t rd, mp_uint asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CA(0x01, 0x23, 0x01, rd, rs)); } +// CM.MVA01S R1S', R2S' +static inline void asm_rv32_opcode_cmmva01s(asm_rv32_t *state, mp_uint_t r1s, mp_uint_t r2s) { + // CMMV: 101011 ... 11 ... 10 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CMMV(0x02, 0x2B, 0x03, r1s, r2s)); +} + +// CM.MVSA01 R1S', R2S' +static inline void asm_rv32_opcode_cmmvsa01(asm_rv32_t *state, mp_uint_t r1s, mp_uint_t r2s) { + // CMMV: 101011 ... 01 ... 10 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CMMV(0x02, 0x2B, 0x01, r1s, r2s)); +} + +// CM.POP {REG_LIST}, IMMEDIATE +static inline void asm_rv32_opcode_cmpop(asm_rv32_t *state, mp_uint_t reg_list, mp_uint_t immediate) { + // CMPP: 10111010 .... .. 10 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CMPP(0x02, 0x2E, 0x02, reg_list, immediate)); +} + // CM.POPRET {REG_LIST}, IMMEDIATE static inline void asm_rv32_opcode_cmpopret(asm_rv32_t *state, mp_uint_t reg_list, mp_uint_t immediate) { - // CMPP: 10111110 ... .. 10 + // CMPP: 10111110 .... .. 10 asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CMPP(0x02, 0x2F, 0x02, reg_list, immediate)); } +// CM.POPRETZ {REG_LIST}, IMMEDIATE +static inline void asm_rv32_opcode_cmpopretz(asm_rv32_t *state, mp_uint_t reg_list, mp_uint_t immediate) { + // CMPP: 10111100 .... .. 10 + asm_rv32_emit_halfword_opcode(state, RV32_ENCODE_TYPE_CMPP(0x02, 0x2F, 0x00, reg_list, immediate)); +} + // CM.PUSH {REG_LIST}, -IMMEDIATE static inline void asm_rv32_opcode_cmpush(asm_rv32_t *state, mp_uint_t reg_list, mp_uint_t immediate) { // CMPP: 10111000 .... .. 10 @@ -748,9 +780,9 @@ static inline uint8_t asm_rv32_allowed_extensions(void) { #define REG_ARG_2 ASM_RV32_REG_A1 #define REG_ARG_3 ASM_RV32_REG_A2 #define REG_ARG_4 ASM_RV32_REG_A3 -#define REG_TEMP0 ASM_RV32_REG_T1 -#define REG_TEMP1 ASM_RV32_REG_T2 -#define REG_TEMP2 ASM_RV32_REG_T3 +#define REG_TEMP0 ASM_RV32_REG_A4 +#define REG_TEMP1 ASM_RV32_REG_A5 +#define REG_TEMP2 ASM_RV32_REG_A6 #define REG_FUN_TABLE ASM_RV32_REG_S1 #define REG_LOCAL_1 ASM_RV32_REG_S3 #define REG_LOCAL_2 ASM_RV32_REG_S2 diff --git a/py/asmthumb.c b/py/asmthumb.c index 58cc7aea880..4226ae28127 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -246,7 +246,7 @@ void asm_thumb_mov_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_src) { void asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src) { assert(reg_dest < ASM_THUMB_REG_R15); // mov[wt] reg_dest, #i16_src - asm_thumb_op32(as, mov_op | ((i16_src >> 1) & 0x0400) | ((i16_src >> 12) & 0xf), ((i16_src << 4) & 0x7000) | (reg_dest << 8) | (i16_src & 0xff)); + asm_thumb_op32(as, mov_op | ((i16_src >> 1) & 0x0400) | ((i16_src >> 12) & 0xf), (((uint16_t)i16_src << 4) & 0x7000) | (reg_dest << 8) | (i16_src & 0xff)); } static void asm_thumb_mov_rlo_i16(asm_thumb_t *as, uint rlo_dest, int i16_src) { diff --git a/py/binary.c b/py/binary.c index 180a13beec7..1ef38d5b3b1 100644 --- a/py/binary.c +++ b/py/binary.c @@ -42,6 +42,12 @@ #define alignof(type) offsetof(struct { char c; type t; }, t) #endif +// MicroPython V1.x truncates integers when writing into arrays, +// MicroPython V2 will raise OverflowError in these cases, same as CPython +// CIRCUITPY-CHANGE: CircuitPython raises OverflowError like CPython, +// rather than truncating like MicroPython V1.x. +#define OVERFLOW_CHECKS (1) + size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { size_t size = 0; int align = 1; @@ -85,10 +91,13 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { // CIRCUITPY-CHANGE: compiler determines size size = sizeof(float); break; + // CIRCUITPY-CHANGE: 'd' is optional + #if MICROPY_PY_DOUBLE_TYPECODE case 'd': // CIRCUITPY-CHANGE: compiler determines size size = sizeof(double); break; + #endif } break; case '@': { @@ -143,10 +152,13 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { align = alignof(float); size = sizeof(float); break; + // CIRCUITPY-CHANGE: 'd' is optional + #if MICROPY_PY_DOUBLE_TYPECODE case 'd': align = alignof(double); size = sizeof(double); break; + #endif } } } @@ -287,8 +299,11 @@ mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) { #if MICROPY_PY_BUILTINS_FLOAT case 'f': return mp_obj_new_float_from_f(((float *)p)[index]); + // CIRCUITPY-CHANGE: 'd' is optional + #if MICROPY_PY_DOUBLE_TYPECODE case 'd': return mp_obj_new_float_from_d(((double *)p)[index]); + #endif #endif // Extension to CPython: array of objects #if MICROPY_PY_STRUCT_UNSAFE_TYPECODES @@ -361,6 +376,8 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte * float f; } fpu = {val}; return mp_obj_new_float_from_f(fpu.f); + // CIRCUITPY-CHANGE: 'd' is optional + #if MICROPY_PY_DOUBLE_TYPECODE } else if (val_type == 'd') { union { uint64_t i; @@ -368,6 +385,7 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte * } fpu = {val}; return mp_obj_new_float_from_d(fpu.f); #endif + #endif } else if (is_signed(val_type)) { if ((long long)MP_SMALL_INT_MIN <= val && val <= (long long)MP_SMALL_INT_MAX) { return mp_obj_new_int((mp_int_t)val); @@ -383,7 +401,21 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte * } } -void mp_binary_set_int(size_t val_sz, bool big_endian, byte *dest, mp_uint_t val) { +void mp_binary_set_int(size_t dest_sz, byte *dest, size_t val_sz, mp_uint_t val, bool big_endian) { + if (dest_sz > val_sz) { + // zero/sign extension if needed + int c = ((mp_int_t)val < 0) ? 0xff : 0x00; + memset(dest, c, dest_sz); + + // big endian: write val_sz bytes at end of 'dest' + if (big_endian) { + dest += dest_sz - val_sz; + } + } else if (dest_sz < val_sz) { + // truncate 'val' into 'dest' + val_sz = dest_sz; + } + if (MP_ENDIANNESS_LITTLE && !big_endian) { memcpy(dest, &val, val_sz); } else if (MP_ENDIANNESS_BIG && big_endian) { @@ -451,126 +483,145 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p val = fp_dp.i64; } else { int be = struct_type == '>'; - mp_binary_set_int(sizeof(uint32_t), be, p, fp_dp.i32[MP_ENDIANNESS_BIG ^ be]); + mp_binary_set_int(sizeof(uint32_t), p, sizeof(uint32_t), fp_dp.i32[MP_ENDIANNESS_BIG ^ be], be); + // Now fall through and copy the second word, below p += sizeof(uint32_t); + size = sizeof(uint32_t); val = fp_dp.i32[MP_ENDIANNESS_LITTLE ^ be]; } break; } #endif #endif - default: { - // CIRCUITPY-CHANGE: add overflow checks - bool signed_type = is_signed(val_type); + default: + // Typecode is a standard integer #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE if (mp_obj_is_exact_type(val_in, &mp_type_int)) { - // It's a longint. - mp_obj_int_buffer_overflow_check(val_in, size, signed_type); - mp_obj_int_to_bytes_impl(val_in, struct_type == '>', size, p); + #if !OVERFLOW_CHECKS + if (size <= sizeof(mp_uint_t)) { + // Aligned store; byte-wise path corrupts word-only registers. + val = mp_obj_int_get_truncated(val_in); + break; + } + #endif + mp_obj_int_to_bytes(val_in, size, p, struct_type == '>', is_signed(val_type), OVERFLOW_CHECKS); return; } #endif - // CIRCUITPY-CHANGE: add overflow checks - { - val = mp_obj_get_int(val_in); - // Small int checking is separate, to be fast. - mp_small_int_buffer_overflow_check(val, size, signed_type); - // zero/sign extend if needed - if (MP_BYTES_PER_OBJ_WORD < 8 && size > sizeof(val)) { - int c = (is_signed(val_type) && (mp_int_t)val < 0) ? 0xff : 0x00; - memset(p, c, size); - if (struct_type == '>') { - p += size - sizeof(val); - } - } - break; - } - } + val = mp_obj_get_int(val_in); + #if OVERFLOW_CHECKS + // CIRCUITPY-CHANGE: small ints are checked too. + mp_small_int_buffer_overflow_check(val, size, is_signed(val_type)); + #endif + break; // Fall through to mp_binary_set_int } - mp_binary_set_int(MIN((size_t)size, sizeof(val)), struct_type == '>', p, val); + mp_binary_set_int(size, p, sizeof(val), val, struct_type == '>'); } +static void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_int_t val); + void mp_binary_set_val_array(char typecode, void *p, size_t index, mp_obj_t val_in) { switch (typecode) { #if MICROPY_PY_BUILTINS_FLOAT case 'f': ((float *)p)[index] = mp_obj_get_float_to_f(val_in); break; + // CIRCUITPY-CHANGE: 'd' is optional + #if MICROPY_PY_DOUBLE_TYPECODE case 'd': ((double *)p)[index] = mp_obj_get_float_to_d(val_in); break; #endif + #endif #if MICROPY_PY_STRUCT_UNSAFE_TYPECODES // Extension to CPython: array of objects case 'O': ((mp_obj_t *)p)[index] = val_in; break; #endif - default: { - // CIRCUITPY-CHANGE: add overflow checks - size_t size = mp_binary_get_size('@', typecode, NULL); - bool signed_type = is_signed(typecode); - + // In all remaining cases the type code is an integer + default: #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE if (mp_obj_is_exact_type(val_in, &mp_type_int)) { - // It's a long int. - mp_obj_int_buffer_overflow_check(val_in, size, signed_type); - mp_obj_int_to_bytes_impl(val_in, MP_ENDIANNESS_BIG, - size, (uint8_t *)p + index * size); + size_t size = mp_binary_get_size('@', typecode, NULL); + #if !OVERFLOW_CHECKS + if (size <= sizeof(mp_int_t)) { + // Aligned store; byte-wise path corrupts word-only registers. + mp_binary_set_val_array_from_int(typecode, p, index, + mp_obj_int_get_truncated(val_in)); + return; + } + #endif + p = (uint8_t *)p + index * size; + byte *dest; + #if OVERFLOW_CHECKS + // If mp_obj_int_to_bytes() might overflow then need to write into a temporary buffer first + assert(size <= sizeof(uint64_t)); + uint64_t temp_buf; + dest = (uint8_t *)&temp_buf; + #else + dest = p; + #endif + mp_obj_int_to_bytes(val_in, size, dest, MP_ENDIANNESS_BIG, is_signed(typecode), OVERFLOW_CHECKS); + #if OVERFLOW_CHECKS + memcpy(p, dest, size); + #endif return; } #endif - // CIRCUITPY-CHANGE: add overflow checks - mp_int_t val = mp_obj_get_int(val_in); - // Small int checking is separate, to be fast. - mp_small_int_buffer_overflow_check(val, size, signed_type); - mp_binary_set_val_array_from_int(typecode, p, index, val); - } + mp_binary_set_val_array_from_int(typecode, p, index, mp_obj_get_int(val_in)); } } -void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_int_t val) { +#if OVERFLOW_CHECKS +#define SET_VAL_AS(TYPE, IS_SIGNED) do { \ + TYPE tmp = val; \ + if ((mp_int_t)tmp == val && (IS_SIGNED || val >= 0)) { \ + ((TYPE *)p)[index] = tmp; \ + } else { \ + goto raise; \ + } \ +} while (0) +#else +#define SET_VAL_AS(TYPE, _IS_SIGNED) do { \ + ((TYPE *)p)[index] = val; \ +} while (0) +#endif + +static void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_int_t val) { switch (typecode) { case 'b': - ((signed char *)p)[index] = val; + SET_VAL_AS(signed char, true); break; case BYTEARRAY_TYPECODE: case 'B': - ((unsigned char *)p)[index] = val; + SET_VAL_AS(unsigned char, false); break; case 'h': - ((short *)p)[index] = val; + SET_VAL_AS(short, true); break; case 'H': - ((unsigned short *)p)[index] = val; + SET_VAL_AS(unsigned short, false); break; case 'i': - ((int *)p)[index] = val; + SET_VAL_AS(int, true); break; case 'I': - ((unsigned int *)p)[index] = val; + SET_VAL_AS(unsigned int, false); break; case 'l': - ((long *)p)[index] = val; + SET_VAL_AS(long, true); break; case 'L': - ((unsigned long *)p)[index] = val; + SET_VAL_AS(unsigned long, false); break; #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE case 'q': - ((long long *)p)[index] = val; + SET_VAL_AS(long long, true); break; case 'Q': - ((unsigned long long *)p)[index] = val; - break; - #endif - #if MICROPY_PY_BUILTINS_FLOAT - case 'f': - ((float *)p)[index] = (float)val; - break; - case 'd': - ((double *)p)[index] = (double)val; + SET_VAL_AS(unsigned long long, false); break; #endif // Extension to CPython: array of pointers @@ -580,4 +631,11 @@ void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_i break; #endif } + + return; + + #if OVERFLOW_CHECKS +raise: + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("integer out of range")); + #endif } diff --git a/py/binary.h b/py/binary.h index 5c645bcaaa9..851dc50110e 100644 --- a/py/binary.h +++ b/py/binary.h @@ -37,10 +37,9 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign); mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index); void mp_binary_set_val_array(char typecode, void *p, size_t index, mp_obj_t val_in); -void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_int_t val); mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte **ptr); void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p_base, byte **ptr); long long mp_binary_get_int(size_t size, bool is_signed, bool big_endian, const byte *src); -void mp_binary_set_int(size_t val_sz, bool big_endian, byte *dest, mp_uint_t val); +void mp_binary_set_int(size_t dest_sz, byte *dest, size_t val_sz, mp_uint_t val, bool big_endian); #endif // MICROPY_INCLUDED_PY_BINARY_H diff --git a/py/builtinhelp.c b/py/builtinhelp.c index 05b51ed4875..d3d7aa402c8 100644 --- a/py/builtinhelp.c +++ b/py/builtinhelp.c @@ -36,6 +36,14 @@ #if MICROPY_PY_BUILTINS_HELP +#if MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS <= 0 +#error "MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS must be more than 0" +#endif + +#if MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH <= 0 +#error "MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH must be more than 0" +#endif + const char mp_help_default_text[] = "Welcome to MicroPython!\n" "\n" @@ -99,12 +107,10 @@ static void mp_help_print_modules(void) { mp_obj_list_sort(1, &list, (mp_map_t *)&mp_const_empty_map); // print the list of modules in a column-first order - #define NUM_COLUMNS (4) - #define COLUMN_WIDTH (18) size_t len; mp_obj_t *items; mp_obj_list_get(list, &len, &items); - unsigned int num_rows = (len + NUM_COLUMNS - 1) / NUM_COLUMNS; + unsigned int num_rows = (len + MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS - 1) / MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS; for (unsigned int i = 0; i < num_rows; ++i) { unsigned int j = i; for (;;) { @@ -113,9 +119,9 @@ static void mp_help_print_modules(void) { if (j >= len) { break; } - int gap = COLUMN_WIDTH - l; + int gap = MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH - l; while (gap < 1) { - gap += COLUMN_WIDTH; + gap += MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH; } while (gap--) { mp_print_str(MP_PYTHON_PRINTER, " "); diff --git a/py/builtinimport.c b/py/builtinimport.c index 5d77ac42859..681ce1458d3 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -77,20 +77,39 @@ static mp_import_stat_t stat_path(vstr_t *path) { // argument. This is the logic that makes .py files take precedent over .mpy // files. This uses stat_path above, rather than mp_import_stat directly, so // that the .frozen path prefix is handled. +// CIRCUITPY-CHANGE: builds that can load native code also try ..mpy +// between .py and .mpy. static mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { mp_import_stat_t stat = stat_path(path); if (stat == MP_IMPORT_STAT_FILE) { return stat; } + #if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_LOAD_NATIVE + size_t base_len = path->len - 3; // drop ".py" + #endif + + #if MICROPY_PERSISTENT_CODE_LOAD_NATIVE + // CIRCUITPY-CHANGE: try '..mpy' first, where is the mpy-cross -march + // name of this build. path is a fixed MICROPY_ALLOC_PATH_MAX buffer, so skip the + // probe when the longer name (plus NUL) would not fit rather than let vstr raise. + if (base_len + sizeof("." MPY_FEATURE_ARCH_NAME ".mpy") <= path->alloc) { + path->len = base_len; + vstr_add_str(path, "." MPY_FEATURE_ARCH_NAME ".mpy"); + if (stat_path(path) == MP_IMPORT_STAT_FILE) { + return MP_IMPORT_STAT_FILE; + } + } + #endif + #if MICROPY_PERSISTENT_CODE_LOAD - // Didn't find .py -- try the .mpy instead by inserting an 'm' into the '.py'. + // Didn't find .py -- try the .mpy instead. // Note: There's no point doing this if it's a frozen path, but adding the check // would be extra code, and no harm letting mp_find_frozen_module fail instead. - vstr_ins_byte(path, path->len - 2, 'm'); - stat = stat_path(path); - if (stat == MP_IMPORT_STAT_FILE) { - return stat; + path->len = base_len; + vstr_add_str(path, ".mpy"); + if (stat_path(path) == MP_IMPORT_STAT_FILE) { + return MP_IMPORT_STAT_FILE; } #endif @@ -259,7 +278,9 @@ static void do_load(mp_module_context_t *module_obj, vstr_t *file) { #endif // MICROPY_MODULE_FROZEN + #if MICROPY_ENABLE_COMPILER || (MICROPY_PERSISTENT_CODE_LOAD && MICROPY_HAS_FILE_READER) qstr file_qstr = qstr_from_str(file_str); + #endif // If we support loading .mpy files then check if the file extension is of // the correct format and, if so, load and execute the file. diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index d91ac8ad23b..e537c821122 100755 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -261,9 +261,15 @@ endif ifeq ($(CIRCUITPY_GNSS),1) SRC_PATTERNS += gnss/% endif +ifeq ($(CIRCUITPY_HARDWAREKEY),1) +SRC_PATTERNS += hardwarekey/% +endif ifeq ($(CIRCUITPY_HASHLIB),1) SRC_PATTERNS += hashlib/% endif +ifeq ($(CIRCUITPY_HMAC),1) +SRC_PATTERNS += hmac/% +endif ifeq ($(CIRCUITPY_I2CDISPLAYBUS),1) SRC_PATTERNS += i2cdisplaybus/% endif @@ -477,9 +483,6 @@ endif ifeq ($(CIRCUITPY_VECTORIO),1) SRC_PATTERNS += vectorio/% endif -ifeq ($(CIRCUITPY_VIDEOCORE),1) -SRC_PATTERNS += videocore/% -endif ifeq ($(CIRCUITPY_WARNINGS),1) SRC_PATTERNS += warnings/% endif @@ -598,6 +601,8 @@ SRC_COMMON_HAL_ALL = \ rtc/__init__.c \ sdioio/SDCard.c \ sdioio/__init__.c \ + hardwarekey/HardwareKey.c \ + hardwarekey/__init__.c \ socketpool/__init__.c \ socketpool/SocketPool.c \ socketpool/Socket.c \ @@ -677,6 +682,7 @@ $(filter $(SRC_PATTERNS), \ paralleldisplaybus/__init__.c \ qrio/PixelPolicy.c \ qrio/QRInfo.c \ + rgbmatrix/RowAddressMode.c \ supervisor/RunReason.c \ supervisor/Runtime.c \ supervisor/StatusBar.c \ @@ -840,6 +846,7 @@ SRC_SHARED_MODULE_ALL = \ rotaryio/IncrementalEncoder.c \ sdcardio/SDCard.c \ sdcardio/__init__.c \ + hardwarekey/HardwareKey.c \ sharpdisplay/SharpMemoryFramebuffer.c \ sharpdisplay/__init__.c \ socket/__init__.c \ @@ -964,7 +971,7 @@ ifeq ($(CIRCUITPY_GIFIO),1) SRC_MOD += $(addprefix lib/AnimatedGIF/, \ gif.c \ ) -$(BUILD)/lib/AnimatedGIF/gif.o: CFLAGS += -DCIRCUITPY +$(BUILD)/lib/AnimatedGIF/gif.o: CFLAGS += -DCIRCUITPY -Wno-unused-but-set-variable endif ifeq ($(CIRCUITPY_JPEGIO),1) @@ -1038,6 +1045,12 @@ SRC_COMMON_HAL_ALL += \ hashlib/__init__.c endif +ifeq ($(CIRCUITPY_HMAC),1) +SRC_SHARED_MODULE_ALL += \ + hmac/HMAC.c \ + hmac/__init__.c +endif + ifeq ($(CIRCUITPY_RGBMATRIX),1) SRC_MOD += $(addprefix lib/protomatter/src/, \ core.c \ diff --git a/py/circuitpy_mpconfig.h b/py/circuitpy_mpconfig.h index 89eeee606f5..85e0b9fb66d 100644 --- a/py/circuitpy_mpconfig.h +++ b/py/circuitpy_mpconfig.h @@ -48,8 +48,20 @@ extern void common_hal_mcu_enable_interrupts(void); #define MICROPY_PY_OS_DUPTERM (0) #define MICROPY_PYEXEC_COMPILE_ONLY (0) #define MICROPY_ROM_TEXT_COMPRESSION (0) +#ifndef MICROPY_VFS_LFS1 #define MICROPY_VFS_LFS1 (0) +#endif +#ifndef MICROPY_VFS_LFS2 #define MICROPY_VFS_LFS2 (0) +#endif + +// CIRCUITPY-CHANGE: Compile-time choice of the CIRCUITPY filesystem. When 1, +// the supervisor mounts littlefs instead of FAT on the block device returned +// by supervisor_flash_*. Ports set this (and MICROPY_VFS_LFS2) from their +// board configuration; the default remains FAT. +#ifndef CIRCUITPY_FILESYSTEM_LITTLEFS +#define CIRCUITPY_FILESYSTEM_LITTLEFS (0) +#endif // Always turn on exit code handling #define MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING (1) @@ -192,6 +204,9 @@ extern void common_hal_mcu_enable_interrupts(void); #define FILESYSTEM_BLOCK_SIZE (512) #define MICROPY_VFS (1) +// CIRCUITPY-CHANGE: CircuitPython's flash/SD block devices are native +// (supervisor/shared/flash.c sets MP_BLOCKDEV_FLAG_NATIVE). +#define MICROPY_VFS_BLOCKDEV_NATIVE (1) #define MICROPY_VFS_FAT (MICROPY_VFS) #define MICROPY_READER_VFS (MICROPY_VFS) @@ -199,7 +214,12 @@ extern void common_hal_mcu_enable_interrupts(void); #define BYTES_PER_WORD (4) +// Bit 0 of a code pointer selects the Thumb instruction set. +#if defined(__thumb__) #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) +#else +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)(p)) +#endif // Track stack usage. Expose results via ustack module. #define MICROPY_MAX_STACK_USAGE (0) @@ -384,6 +404,11 @@ typedef long mp_off_t; #define CIRCUITPY_CONSOLE_UART_HEXDUMP(...) (void)0 #endif +// Normally set from CIRCUITPY_BUSIO_SPI_ASYNC in the .mk files. +#ifndef CIRCUITPY_BUSIO_SPI_ASYNC +#define CIRCUITPY_BUSIO_SPI_ASYNC (0) +#endif + // These CIRCUITPY_xxx values should all be defined in the *.mk files as being on or off. // So if any are not defined in *.mk, they'll throw an error here. @@ -392,16 +417,13 @@ typedef long mp_off_t; #define CIRCUITPY_DISPLAY_LIMIT (1) #endif -// Display area buffer size in bytes for _refresh_area() VLA. -// Allocated on stack; boards with larger displays can override per-board. -// Default 512 bytes = 128 uint32_t words. +// Normally set from CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE in the .mk files. #ifndef CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE #define CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE (512) #endif #else #define CIRCUITPY_DISPLAY_LIMIT (0) -#define CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE (0) #endif // This is not a top-level module; it's microcontroller.nvm. @@ -418,6 +440,19 @@ extern const struct _mp_obj_module_t nvm_module; #define ULAB_NUMPY_HAS_RANDOM_MODULE (0) #endif +// The scipy.integrate module is about 6 kB. +#ifndef ULAB_SCIPY_HAS_INTEGRATE_MODULE +#define ULAB_SCIPY_HAS_INTEGRATE_MODULE (0) +#endif + +// The ndarray modulo operators are about 4 kB. +#ifndef NDARRAY_HAS_BINARY_OP_MODULO +#define NDARRAY_HAS_BINARY_OP_MODULO (0) +#endif +#ifndef NDARRAY_HAS_INPLACE_MODULO +#define NDARRAY_HAS_INPLACE_MODULO (0) +#endif + #if CIRCUITPY_ULAB // ulab requires reverse special methods #if defined(MICROPY_PY_REVERSE_SPECIAL_METHODS) && !MICROPY_PY_REVERSE_SPECIAL_METHODS @@ -696,6 +731,13 @@ void background_callback_run_all(void); #error "CIRCUITPY_BOOT_BUTTON and CIRCUITPY_BOOT_BUTTON_NO_GPIO are mutually exclusive" #endif +// Boards that define CIRCUITPY_BOOT_BUTTON can set +// CIRCUITPY_BOOT_BUTTON_ACTIVE_HIGH to 1 when the button pin reads high while +// pressed. The default is active-low (the pin reads low while pressed). +#ifndef CIRCUITPY_BOOT_BUTTON_ACTIVE_HIGH +#define CIRCUITPY_BOOT_BUTTON_ACTIVE_HIGH (0) +#endif + #if defined(__GNUC__) && !defined(__ZEPHYR__) #if __GNUC__ < CIRCUITPY_MIN_GCC_VERSION // (the 3 level scheme here is required to get expansion & stringization diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 178f424d5c0..3678625455a 100755 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -219,6 +219,13 @@ CFLAGS += -DCIRCUITPY_BUSDEVICE=$(CIRCUITPY_BUSDEVICE) CIRCUITPY_BUILTINS_POW3 ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_BUILTINS_POW3=$(CIRCUITPY_BUILTINS_POW3) +# Bulk reset: when enabled, pins and buses registered with never_reset survive a +# soft reset (the port resets all remaining pins in bulk). When disabled, ports +# rely solely on GC finalizers to release hardware, so never_reset is compiled +# out. Ports that do not perform a bulk pin reset should set this to 0. +CIRCUITPY_BULK_RESET ?= 1 +CFLAGS += -DCIRCUITPY_BULK_RESET=$(CIRCUITPY_BULK_RESET) + CIRCUITPY_BUSIO ?= 1 CFLAGS += -DCIRCUITPY_BUSIO=$(CIRCUITPY_BUSIO) @@ -231,6 +238,11 @@ CFLAGS += -DCIRCUITPY_BUSIO_I2C=$(CIRCUITPY_BUSIO_I2C) CIRCUITPY_BUSIO_SPI ?= $(CIRCUITPY_BUSIO) CFLAGS += -DCIRCUITPY_BUSIO_SPI=$(CIRCUITPY_BUSIO_SPI) +# The port implements common_hal_busio_spi_write_start() and common_hal_busio_spi_end(), so a +# display on a FourWire bus can send one strip while the next is being composited. +CIRCUITPY_BUSIO_SPI_ASYNC ?= 0 +CFLAGS += -DCIRCUITPY_BUSIO_SPI_ASYNC=$(CIRCUITPY_BUSIO_SPI_ASYNC) + CIRCUITPY_BUSIO_UART ?= $(CIRCUITPY_BUSIO) CFLAGS += -DCIRCUITPY_BUSIO_UART=$(CIRCUITPY_BUSIO_UART) @@ -270,6 +282,10 @@ CFLAGS += -DCIRCUITPY_COUNTIO=$(CIRCUITPY_COUNTIO) CIRCUITPY_DISPLAYIO ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_DISPLAYIO=$(CIRCUITPY_DISPLAYIO) +# Stack bytes a display refresh composes at once. Bigger means fewer, larger pieces per refresh. +CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE ?= 512 +CFLAGS += -DCIRCUITPY_DISPLAY_AREA_BUFFER_SIZE=$(CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE) + CIRCUITPY_BUSDISPLAY ?= $(CIRCUITPY_DISPLAYIO) CFLAGS += -DCIRCUITPY_BUSDISPLAY=$(CIRCUITPY_BUSDISPLAY) @@ -372,6 +388,12 @@ CFLAGS += -DCIRCUITPY_HASHLIB_MBEDTLS=$(CIRCUITPY_HASHLIB_MBEDTLS) CIRCUITPY_HASHLIB_MBEDTLS_ONLY ?= $(call enable-if-all,$(CIRCUITPY_HASHLIB_MBEDTLS) $(call enable-if-not,$(CIRCUITPY_SSL))) CFLAGS += -DCIRCUITPY_HASHLIB_MBEDTLS_ONLY=$(CIRCUITPY_HASHLIB_MBEDTLS_ONLY) +# hmac: CPython-compatible HMAC, backed by PSA Crypto. Available wherever a full PSA +# crypto build with HMAC is already present (SSL builds, espressif's ESP-IDF mbedtls); +# the HASHLIB_MBEDTLS_ONLY subset does not include the PSA MAC driver yet. +CIRCUITPY_HMAC ?= $(call enable-if-all,$(CIRCUITPY_HASHLIB_MBEDTLS) $(call enable-if-not,$(CIRCUITPY_HASHLIB_MBEDTLS_ONLY))) +CFLAGS += -DCIRCUITPY_HMAC=$(CIRCUITPY_HMAC) + # Always zero because it is for Zephyr only CFLAGS += -DCIRCUITPY_HOSTNETWORK=0 @@ -558,6 +580,11 @@ CFLAGS += -DCIRCUITPY_SDCARDIO=$(CIRCUITPY_SDCARDIO) CIRCUITPY_SDIOIO ?= 0 CFLAGS += -DCIRCUITPY_SDIOIO=$(CIRCUITPY_SDIOIO) +# hardwarekey: cryptographic operations with hardware-held, non-readable keys. +# Off unless a port provides a common-hal/hardwarekey backend. +CIRCUITPY_HARDWAREKEY ?= 0 +CFLAGS += -DCIRCUITPY_HARDWAREKEY=$(CIRCUITPY_HARDWAREKEY) + CIRCUITPY_BLE_SERIAL_SERVICE ?= 0 CFLAGS += -DCIRCUITPY_BLE_SERIAL_SERVICE=$(CIRCUITPY_BLE_SERIAL_SERVICE) @@ -771,15 +798,11 @@ CFLAGS += -DCIRCUITPY_ZLIB=$(CIRCUITPY_ZLIB) CIRCUITPY_ULAB ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_ULAB=$(CIRCUITPY_ULAB) -# whether to use -Os optimization on files in ulab +# Enabling this compiles ulab with -Os and uses function-pointer dispatch for +# ndarray binary operators. This saves about 4 kB of flash but makes +# element-wise array arithmetic roughly 1.5x slower. CIRCUITPY_ULAB_OPTIMIZE_SIZE ?= 0 -# CIRCUITPY_VIDEOCORE is handled in the broadcom tree. -# Only for Broadcom chips. -# Assume not a Broadcom build. -CIRCUITPY_VIDEOCORE ?= 0 -CFLAGS += -DCIRCUITPY_VIDEOCORE=$(CIRCUITPY_VIDEOCORE) - CIRCUITPY_WARNINGS ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_WARNINGS=$(CIRCUITPY_WARNINGS) diff --git a/py/compile.c b/py/compile.c index e16e2f131e7..ae3526e2f2a 100644 --- a/py/compile.c +++ b/py/compile.c @@ -2423,6 +2423,14 @@ static void compile_trailer_paren_helper(compiler_t *comp, mp_parse_node_t pn_ar compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("* arg after **")); return; } + if (n_keyword) { + // Support for *arg after kwarg is a CPython feature omitted + // from MicroPython in order to reduce code size. See + // https://github.com/micropython/micropython/issues/11439 for + // more info. + compile_syntax_error(comp, (mp_parse_node_t)pns_arg, MP_ERROR_TEXT("* arg after kwarg")); + return; + } #if MICROPY_DYNAMIC_COMPILER if (i >= (size_t)mp_dynamic_compiler.small_int_bits - 1) #else @@ -3664,7 +3672,7 @@ void mp_compile_to_raw_code(mp_parse_tree_t *parse_tree, qstr source_file, bool #if MICROPY_DEBUG_PRINTERS // now that the module context is valid, the raw codes can be printed - if (mp_verbose_flag >= 2) { + if (MP_STATE_VM(mp_verbose_flag) >= 2) { for (scope_t *s = comp->scope_head; s != NULL; s = s->next) { mp_raw_code_t *rc = s->raw_code; if (rc->kind == MP_CODE_BYTECODE) { diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 3902acbd0b3..cab609b13b0 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -1,7 +1,8 @@ # Makefile fragment for generating native .mpy files from C source # MPY_DIR must be set to the top of the MicroPython source tree -BUILD ?= build +BUILD ?= build-$(ARCH) +CC = $(CROSS)gcc ECHO = @echo RM = /bin/rm @@ -39,7 +40,7 @@ MPY_CROSS_FLAGS += -march=$(ARCH) SRC_O += $(addprefix $(BUILD)/, $(patsubst %.c,%.o,$(filter %.c,$(SRC))) $(patsubst %.S,%.o,$(filter %.S,$(SRC)))) SRC_MPY += $(addprefix $(BUILD)/, $(patsubst %.py,%.mpy,$(filter %.py,$(SRC)))) -CLEAN_EXTRA += $(MOD).mpy .mpy_ld_cache +CLEAN_EXTRA += $(MOD).mpy .mpy_ld_cache-$(ARCH) ################################################################################ # Architecture configuration @@ -47,14 +48,14 @@ CLEAN_EXTRA += $(MOD).mpy .mpy_ld_cache ifeq ($(ARCH),x86) # x86 -CROSS = -CFLAGS_ARCH += -m32 -fno-stack-protector +CROSS = i686-linux-gnu- +CFLAGS_ARCH += -fno-stack-protector MICROPY_FLOAT_IMPL ?= double else ifeq ($(ARCH),x64) # x64 -CROSS = +CROSS = x86_64-linux-gnu- CFLAGS_ARCH += -fno-stack-protector MICROPY_FLOAT_IMPL ?= double @@ -106,53 +107,57 @@ else ifeq ($(ARCH),rv32imc) # rv32imc CROSS = riscv64-unknown-elf- CFLAGS_ARCH += -march=rv32imac -mabi=ilp32 -mno-relax -# If Picolibc is available then select it explicitly. Ubuntu 24.04 ships its -# bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default -# is "nosys" so a value must be provided. To avoid having per-distro -# workarounds, always select Picolibc if available. -PICOLIBC_SPECS := $(shell $(CROSS)gcc --print-file-name=picolibc.specs) -ifneq ($(PICOLIBC_SPECS),picolibc.specs) -CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS) -USE_PICOLIBC := 1 -PICOLIBC_ARCH := rv32imac -PICOLIBC_ABI := ilp32 -endif - MICROPY_FLOAT_IMPL ?= none +PICOLIBC_BASE := riscv64-unknown-elf +PICOLIBC_TARGET := rv32imac/ilp32 else ifeq ($(ARCH),rv64imc) # rv64imc CROSS = riscv64-unknown-elf- CFLAGS_ARCH += -march=rv64imac -mabi=lp64 -mno-relax +MICROPY_FLOAT_IMPL ?= none +PICOLIBC_BASE := riscv64-unknown-elf +PICOLIBC_TARGET := rv64imac/lp64 + +else +$(error architecture '$(ARCH)' not supported) +endif + +ifneq ($(findstring -musl,$(shell $(CC) -dumpmachine)),) +USE_MUSL := 1 +endif + +ifeq ($(ARCH),$(filter $(ARCH),rv32imc rv64imc)) # If Picolibc is available then select it explicitly. Ubuntu 24.04 ships its # bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default # is "nosys" so a value must be provided. To avoid having per-distro # workarounds, always select Picolibc if available. -PICOLIBC_SPECS := $(shell $(CROSS)gcc --print-file-name=picolibc.specs) +PICOLIBC_SPECS := $(shell $(CC) --print-file-name=picolibc.specs) ifneq ($(PICOLIBC_SPECS),picolibc.specs) +# LLVM toolchains supporting more than one target seem to ignore the `-march` +# flag passed when looking up the specs file, so if your system has Picolibc +# libraries for more than one architectures supported by the compiler the +# lookup will return the first available file. +# +# For example, on Ubuntu 24.02 if you have both `picolibc-aarch64-linux-gnu` +# and `picolibc-riscv64-unknown-elf` packages installed, the Qualcomm LLVM +# toolchain (which supports both AArch64 and RISC-V 64) will always return the +# AArch64 picolibc specs even when building for RISC-V. +ifeq ($(shell grep -q "$(PICOLIBC_BASE)" "$(PICOLIBC_SPECS)"; echo $$?),0) CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS) USE_PICOLIBC := 1 -PICOLIBC_ARCH := rv64imac -PICOLIBC_ABI := lp64 endif - -MICROPY_FLOAT_IMPL ?= none - -else -$(error architecture '$(ARCH)' not supported) endif - -ifneq ($(findstring -musl,$(shell $(CROSS)gcc -dumpmachine)),) -USE_MUSL := 1 endif MICROPY_FLOAT_IMPL_UPPER = $(shell echo $(MICROPY_FLOAT_IMPL) | tr '[:lower:]' '[:upper:]') CFLAGS += $(CFLAGS_ARCH) -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER) +CFLAGS += $(CFLAGS_EXTRA) ifeq ($(LINK_RUNTIME),1) # All of these picolibc-specific directives are here to work around a -# limitation of Ubuntu 22.04's RISC-V bare metal toolchain. In short, the +# limitation of Ubuntu 24.04's RISC-V bare metal toolchain. In short, the # specific version of GCC in use (10.2.0) does not seem to take into account # extra paths provided by an explicitly passed specs file when performing name # resolution via `--print-file-name`. @@ -163,7 +168,7 @@ ifeq ($(LINK_RUNTIME),1) # flags that are passed to GCC. The `PICOLIBC_ROOT` environment variable is # checked to override the starting point for the library file search, and if # it is not set then the default value is used, assuming that this is running -# on an Ubuntu 22.04 machine. +# on an Ubuntu 24.04 machine. # # This should be revised when the CI base image is updated to a newer Ubuntu # version (that hopefully contains a newer RISC-V compiler) or to another Linux @@ -175,21 +180,37 @@ LIBM_NAME := libc.a else LIBM_NAME := libm.a endif -LIBGCC_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-libgcc-file-name)) -LIBM_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-file-name=$(LIBM_NAME))) +# Clang will output the path to libclang_rt.builtins.a instead. The problem is +# that some symbols are duplicated between the builtins library and libc.a. In +# these cases let's leave it to the user to figure out how to handle this for +# the time being. +TOOLCHAIN_LIBGCC := $(realpath $(shell $(CC) $(CFLAGS) --print-libgcc-file-name)) +ifneq ($(findstring clang,$(shell $(CC) --version)),clang) +LIBGCC_PATH = $(TOOLCHAIN_LIBGCC) +else +ifneq ($(LINK_CLANG_CLANGRT),0) +LIBGCC_PATH = $(TOOLCHAIN_LIBGCC) +else +LIBGCC_PATH = +endif +endif +LIBM_PATH := $(realpath $(shell $(CC) $(CFLAGS) --print-file-name=$(LIBM_NAME))) ifeq ($(USE_PICOLIBC),1) ifeq ($(LIBM_PATH),) -# The CROSS toolchain prefix usually ends with a dash, but that may not be -# always the case. If the prefix ends with a dash it has to be taken out as -# Picolibc's architecture directory won't have it in its name. GNU Make does -# not have any facility to perform character-level text manipulation so we -# shell out to sed. -CROSS_PREFIX := $(shell echo $(CROSS) | sed -e 's/-$$//') -PICOLIBC_ROOT ?= /usr/lib/picolibc/$(CROSS_PREFIX)/lib -LIBM_PATH := $(PICOLIBC_ROOT)/$(PICOLIBC_ARCH)/$(PICOLIBC_ABI)/$(LIBM_NAME) +PICOLIBC_ROOT ?= /usr/lib/picolibc/$(PICOLIBC_BASE)/lib +LIBM_PATH := $(PICOLIBC_ROOT)/$(PICOLIBC_TARGET)/$(LIBM_NAME) endif endif -MPY_LD_FLAGS += $(addprefix -l, $(LIBGCC_PATH) $(LIBM_PATH)) +ifneq ($(LINK_CLANG_LIBC),) +ifeq ($(findstring clang,$(shell $(CC) --version)),clang) +LIBC_PATH := $(realpath $(shell $(CC) $(CFLAGS) --print-file-name=libc.a)) +else +LIBC_PATH = +endif +else +LIBC_PATH = +endif +MPY_LD_FLAGS += $(addprefix -l, $(LIBGCC_PATH) $(LIBM_PATH) $(LIBC_PATH)) endif ifneq ($(MPY_EXTERN_SYM_FILE),) MPY_LD_FLAGS += --externs "$(realpath $(MPY_EXTERN_SYM_FILE))" @@ -198,8 +219,6 @@ ifneq ($(ARCH_FLAGS),) MPY_LD_FLAGS += --arch-flags "$(ARCH_FLAGS)" endif -CFLAGS += $(CFLAGS_EXTRA) - ################################################################################ # Build rules @@ -224,12 +243,12 @@ $(CONFIG_H): $(SRC) # Build .o from .c source files $(BUILD)/%.o: %.c $(CONFIG_H) Makefile $(ECHO) "CC $<" - $(Q)$(CROSS)gcc $(CFLAGS) -o $@ -c $< + $(Q)$(CC) $(CFLAGS) -o $@ -c $< # Build .o from .S source files $(BUILD)/%.o: %.S $(CONFIG_H) Makefile $(ECHO) "AS $<" - $(Q)$(CROSS)gcc $(CFLAGS) -o $@ -c $< + $(Q)$(CC) $(CFLAGS) -o $@ -c $< # Build .mpy from .py source files $(BUILD)/%.mpy: %.py @@ -237,11 +256,11 @@ $(BUILD)/%.mpy: %.py $(Q)$(MPY_CROSS) $(MPY_CROSS_FLAGS) -o $@ $< # Build native .mpy from object files -$(BUILD)/$(MOD).native.mpy: $(SRC_O) +$(BUILD)/$(MOD).mpy: $(SRC_O) $(ECHO) "LINK $<" $(Q)$(MPY_LD) --arch $(ARCH) --qstrs $(CONFIG_H) $(MPY_LD_FLAGS) -o $@ $^ # Build final .mpy from all intermediate .mpy files -$(MOD).mpy: $(BUILD)/$(MOD).native.mpy $(SRC_MPY) +$(MOD).mpy: $(BUILD)/$(MOD).mpy $(SRC_MPY) $(ECHO) "GEN $@" $(Q)$(MPY_TOOL) --merge -o $@ $^ diff --git a/py/emitbc.c b/py/emitbc.c index 0bcac8a1676..5f133ab070b 100644 --- a/py/emitbc.c +++ b/py/emitbc.c @@ -432,6 +432,12 @@ void mp_emit_bc_set_source_line(emit_t *emit, mp_uint_t source_line) { // If we compile with -O3, don't store line numbers. return; } + #if MICROPY_DYNAMIC_COMPILER + if (!mp_dynamic_compiler.include_source_lines) { + // Don't store line numbers if explicitly disabled. + return; + } + #endif if (source_line > emit->last_source_line) { mp_uint_t bytes_to_skip = emit->bytecode_offset - emit->last_source_line_offset; mp_uint_t lines_to_skip = source_line - emit->last_source_line; diff --git a/py/emitglue.c b/py/emitglue.c index 48834b3218d..3b479079af0 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -50,10 +50,6 @@ #define DEBUG_OP_printf(...) (void)0 #endif -#if MICROPY_DEBUG_PRINTERS -mp_uint_t mp_verbose_flag = 0; -#endif - mp_raw_code_t *mp_emit_glue_new_raw_code(void) { mp_raw_code_t *rc = m_new0(mp_raw_code_t, 1); rc->kind = MP_CODE_RESERVED; diff --git a/py/emitinlinerv32.c b/py/emitinlinerv32.c index e81b152087d..b175ae0ddc8 100644 --- a/py/emitinlinerv32.c +++ b/py/emitinlinerv32.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -505,7 +506,7 @@ static bool serialise_argument(emit_inline_asm_t *emit, const opcode_t *opcode, return false; } - mp_uint_t immediate = mp_obj_get_int_truncated(object) << shift; + mp_uint_t immediate = ((mp_uint_t)mp_obj_get_int_truncated(object)) << shift; if (kind & U) { if (!is_in_unsigned_mask(mask, immediate)) { goto out_of_range; @@ -693,6 +694,192 @@ static void handle_opcode(emit_inline_asm_t *emit, const opcode_t *opcode_data, } } +static bool extract_register_list(emit_inline_asm_t *emit, qstr opcode, mp_parse_node_t node, mp_uint_t *reglist) { + assert(reglist != NULL && "Register list pointer is NULL."); + + // As per §28.9, valid register list values are as follows: + // + // {ra}, {ra, s0}, {ra, s0-s1}, {ra, s0-s2}, ..., {ra, s0-s8}, + // {ra, s0-s9}, {ra, s0-s11} + // + // {ra, s0-s10} is *not* valid + + // case 1: {ra} + // PN_atom_brace { ID("ra") } + // + // case 2: {ra,s0} -> + // PN_atom_brace { PN_dictorsetmaker { ID("ra") + // PN_dictorsetmaker_list { ID("s0") } } } + // + // case 3: {ra,s0-s1} -> + // PN_atom_brace { PN_dictorsetmaker { ID("ra") + // PN_dictorsetmaker_list { PN_arith_expr { + // ID("s0") TOKEN(MP_TOKEN_OP_MINUS) ID("s1") } } } } + + if (!MP_PARSE_NODE_IS_STRUCT_KIND(node, PN_atom_brace) || + MP_PARSE_NODE_STRUCT_NUM_NODES((mp_parse_node_struct_t *)node) != 1) { + return false; + } + + mp_parse_node_struct_t *nodes = (mp_parse_node_struct_t *)node; + mp_uint_t register_id = 0; + + if (MP_PARSE_NODE_IS_ID(nodes->nodes[0])) { + if (!parse_register_node(nodes->nodes[0], ®ister_id, false)) { + return false; + } + *reglist = 4; + return register_id == ASM_RV32_REG_RA; + } + + if (!MP_PARSE_NODE_IS_STRUCT_KIND(nodes->nodes[0], PN_dictorsetmaker) || + MP_PARSE_NODE_STRUCT_NUM_NODES((mp_parse_node_struct_t *)nodes->nodes[0]) != 2) { + return false; + } + nodes = (mp_parse_node_struct_t *)nodes->nodes[0]; + if (!MP_PARSE_NODE_IS_ID(nodes->nodes[0]) || + !MP_PARSE_NODE_IS_STRUCT_KIND(nodes->nodes[1], PN_dictorsetmaker_list) || + !parse_register_node(nodes->nodes[0], ®ister_id, false) || + register_id != ASM_RV32_REG_RA) { + return false; + } + mp_parse_node_t *list_nodes; + size_t list_nodes_count = mp_parse_node_extract_list(&nodes->nodes[1], PN_dictorsetmaker_list2, &list_nodes); + if (list_nodes_count != 1 || !MP_PARSE_NODE_IS_STRUCT_KIND(list_nodes[0], PN_dictorsetmaker_list)) { + return false; + } + nodes = (mp_parse_node_struct_t *)list_nodes[0]; + if (MP_PARSE_NODE_STRUCT_NUM_NODES(nodes) != 1) { + return false; + } + if (MP_PARSE_NODE_IS_ID(nodes->nodes[0])) { + if (!parse_register_node(nodes->nodes[0], ®ister_id, false) || + register_id != ASM_RV32_REG_S0) { + return false; + } + *reglist = 5; + return true; + } + + if (MP_PARSE_NODE_IS_STRUCT_KIND(nodes->nodes[0], PN_arith_expr)) { + nodes = (mp_parse_node_struct_t *)nodes->nodes[0]; + if (MP_PARSE_NODE_STRUCT_NUM_NODES(nodes) != 3 || + !MP_PARSE_NODE_IS_ID(nodes->nodes[0]) || + !MP_PARSE_NODE_IS_TOKEN_KIND(nodes->nodes[1], MP_TOKEN_OP_MINUS) || + !MP_PARSE_NODE_IS_ID(nodes->nodes[2])) { + return false; + } + if (!parse_register_node(nodes->nodes[0], ®ister_id, false) || + register_id != ASM_RV32_REG_S0) { + return false; + } + if (!parse_register_node(nodes->nodes[2], ®ister_id, false) || + register_id == ASM_RV32_REG_S10) { + return false; + } + if (register_id == ASM_RV32_REG_S1) { + *reglist = 6; + return true; + } + if (register_id >= ASM_RV32_REG_S2 && register_id <= ASM_RV32_REG_S11) { + *reglist = 7 + MIN(register_id, ASM_RV32_REG_S10) - ASM_RV32_REG_S2; + return true; + } + } + + return false; +} + +static const qstr_short_t ZCMP_OPCODE_NAMES[] = { + MP_QSTR_cm_push, MP_QSTR_cm_pop, MP_QSTR_cm_popret, + MP_QSTR_cm_popretz, MP_QSTR_cm_mva01s, MP_QSTR_cm_mvsa01, +}; + +static const void *ZCMP_OPCODE_HANDLERS[] = { + asm_rv32_opcode_cmpush, asm_rv32_opcode_cmpop, + asm_rv32_opcode_cmpopret, asm_rv32_opcode_cmpopretz, + asm_rv32_opcode_cmmva01s, asm_rv32_opcode_cmmvsa01, +}; + +typedef void (*call_xi_t)(asm_rv32_t *state, mp_uint_t register_list, mp_int_t adjustment); +typedef void (*call_ss_t)(asm_rv32_t *state, mp_uint_t r1, mp_uint_t r2); + +static bool handle_zcmp_opcode(emit_inline_asm_t *emit, qstr opcode, mp_parse_node_t *argument_nodes) { + mp_uint_t argument_index = 0; + + for (size_t index = 0; index < MP_ARRAY_SIZE(ZCMP_OPCODE_NAMES); index++) { + if (ZCMP_OPCODE_NAMES[index] != opcode) { + continue; + } + const void *handler = ZCMP_OPCODE_HANDLERS[index]; + if (opcode == MP_QSTR_cm_mva01s || opcode == MP_QSTR_cm_mvsa01) { + mp_uint_t register_lhs = 0; + mp_uint_t register_rhs = 0; + + if (!parse_register_node(argument_nodes[0], ®ister_lhs, false) || + ((1U << register_lhs) & 0x00FC0300) == 0) { + goto invalid_s_register; + } + + if (!parse_register_node(argument_nodes[1], ®ister_rhs, false) || + ((1U << register_rhs) & 0x00FC0300) == 0) { + argument_index = 1; + goto invalid_s_register; + } + + if (register_lhs == register_rhs) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q': registers must be different"), + opcode)); + return false; + } + + ((call_ss_t)handler)(&emit->as, register_lhs, register_rhs); + return true; + } + + mp_uint_t register_list; + if (!extract_register_list(emit, opcode, argument_nodes[0], ®ister_list)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q': malformed register list"), + opcode)); + return false; + } + + mp_obj_t stack_adjustment_object; + if (!mp_parse_node_get_int_maybe(argument_nodes[1], &stack_adjustment_object)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + ET_WRONG_ARGUMENT_KIND, opcode, 2, MP_QSTR_integer)); + return false; + } + mp_int_t stack_adjustment = mp_obj_get_int(stack_adjustment_object); + // Either 0, 16, 32, or 48. + if ((abs((int32_t)stack_adjustment) & ~0x30U) != 0 || + ((opcode == MP_QSTR_cm_push) && stack_adjustment > 0) || + ((opcode != MP_QSTR_cm_push) && stack_adjustment < 0)) { + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q': invalid stack adjustment"), + opcode)); + return false; + } + ((call_xi_t)handler)(&emit->as, register_list, abs((int32_t)stack_adjustment)); + return true; + } + + return false; + +invalid_s_register: + emit_inline_rv32_error_exc(emit, + mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, + MP_ERROR_TEXT("opcode '%q' argument %d: wrong register(s)"), + opcode, argument_index + 1)); + return false; +} + static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uint_t arguments_count, mp_parse_node_t *argument_nodes) { const opcode_t *opcode_data = NULL; for (mp_uint_t index = 0; index < MP_ARRAY_SIZE(OPCODES); index++) { @@ -702,6 +889,10 @@ static void emit_inline_rv32_opcode(emit_inline_asm_t *emit, qstr opcode, mp_uin } } + if ((asm_rv32_allowed_extensions() & RV32_EXT_ZCMP) && !opcode_data && (arguments_count == 2) && handle_zcmp_opcode(emit, opcode, argument_nodes)) { + return; + } + if (!opcode_data || (asm_rv32_allowed_extensions() & opcode_data->required_extensions) != opcode_data->required_extensions) { emit_inline_rv32_error_exc(emit, mp_obj_new_exception_msg_varg(&mp_type_SyntaxError, MP_ERROR_TEXT("invalid RV32 instruction '%q'"), opcode)); diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index d6596337ae5..9c82921edfc 100644 --- a/py/emitinlinethumb.c +++ b/py/emitinlinethumb.c @@ -364,24 +364,15 @@ static int get_arg_label(emit_inline_asm_t *emit, const char *op, mp_parse_node_ return 0; } -typedef struct _cc_name_t { byte cc; - byte name[2]; -} cc_name_t; -static const cc_name_t cc_name_table[] = { - { ASM_THUMB_CC_EQ, { 'e', 'q' }}, - { ASM_THUMB_CC_NE, { 'n', 'e' }}, - { ASM_THUMB_CC_CS, { 'c', 's' }}, - { ASM_THUMB_CC_CC, { 'c', 'c' }}, - { ASM_THUMB_CC_MI, { 'm', 'i' }}, - { ASM_THUMB_CC_PL, { 'p', 'l' }}, - { ASM_THUMB_CC_VS, { 'v', 's' }}, - { ASM_THUMB_CC_VC, { 'v', 'c' }}, - { ASM_THUMB_CC_HI, { 'h', 'i' }}, - { ASM_THUMB_CC_LS, { 'l', 's' }}, - { ASM_THUMB_CC_GE, { 'g', 'e' }}, - { ASM_THUMB_CC_LT, { 'l', 't' }}, - { ASM_THUMB_CC_GT, { 'g', 't' }}, - { ASM_THUMB_CC_LE, { 'l', 'e' }}, +#define ENCODE_CC(c1, c2) (((uint16_t)(c1) << 8) | (uint16_t)(c2)) + +// Positions in the table match the condition code value. +static const uint16_t CONDITION_CODES[] = { + ENCODE_CC('e', 'q'), ENCODE_CC('n', 'e'), ENCODE_CC('c', 's'), + ENCODE_CC('c', 'c'), ENCODE_CC('m', 'i'), ENCODE_CC('p', 'l'), + ENCODE_CC('v', 's'), ENCODE_CC('v', 'c'), ENCODE_CC('h', 'i'), + ENCODE_CC('l', 's'), ENCODE_CC('g', 'e'), ENCODE_CC('l', 't'), + ENCODE_CC('g', 't'), ENCODE_CC('l', 'e'), }; typedef struct _format_4_op_t { byte op; @@ -574,9 +565,11 @@ static void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a || (op_len == 5 && op_str[3] == '_' && (op_str[4] == 'n' || (ARMV7M && op_str[4] == 'w'))))) { mp_uint_t cc = -1; - for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(cc_name_table); i++) { - if (op_str[1] == cc_name_table[i].name[0] && op_str[2] == cc_name_table[i].name[1]) { - cc = cc_name_table[i].cc; + uint16_t condition_code = ENCODE_CC(op_str[1], op_str[2]); + for (size_t i = 0; i < MP_ARRAY_SIZE(CONDITION_CODES); ++i) { + if (condition_code == CONDITION_CODES[i]) { + cc = i; + break; } } if (cc == (mp_uint_t)-1) { @@ -592,12 +585,14 @@ static void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a } } else if (ARMV7M && op_str[0] == 'i' && op_str[1] == 't') { const char *arg_str = get_arg_str(pn_args[0]); + if (strlen(arg_str) != 2) { + goto unknown_op; + } mp_uint_t cc = -1; - for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(cc_name_table); i++) { - if (arg_str[0] == cc_name_table[i].name[0] - && arg_str[1] == cc_name_table[i].name[1] - && arg_str[2] == '\0') { - cc = cc_name_table[i].cc; + uint16_t condition_code = ENCODE_CC(arg_str[0], arg_str[1]); + for (size_t i = 0; i < MP_ARRAY_SIZE(CONDITION_CODES); ++i) { + if (condition_code == CONDITION_CODES[i]) { + cc = i; break; } } diff --git a/py/emitnative.c b/py/emitnative.c index 06c2f25b60c..9ac62f6d362 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -246,6 +246,7 @@ typedef enum { VTYPE_BUILTIN_CAST = 0x70 | MP_NATIVE_TYPE_OBJ, } vtype_kind_t; +#if MICROPY_ERROR_REPORTING != MICROPY_ERROR_REPORTING_NONE static qstr vtype_to_qstr(vtype_kind_t vtype) { switch (vtype) { case VTYPE_PYOBJ: @@ -269,6 +270,7 @@ static qstr vtype_to_qstr(vtype_kind_t vtype) { return MP_QSTR_None; } } +#endif typedef struct _stack_info_t { vtype_kind_t vtype; @@ -2378,22 +2380,23 @@ static void emit_native_unary_op(emit_t *emit, mp_unary_op_t op) { if (op == MP_UNARY_OP_POSITIVE) { // No-operation, just leave the argument on the stack. } else if (op == MP_UNARY_OP_NEGATIVE) { - int reg = REG_RET; - emit_pre_pop_reg_flexible(emit, &vtype, ®, reg, reg); - ASM_NEG_REG(emit->as, reg); - emit_post_push_reg(emit, vtype, reg); + emit_pre_pop_reg(emit, &vtype, REG_RET); + ASM_NEG_REG(emit->as, REG_RET); + emit_post_push_reg(emit, vtype, REG_RET); } else if (op == MP_UNARY_OP_INVERT) { + emit_pre_pop_reg(emit, &vtype, REG_RET); #ifdef ASM_NOT_REG - int reg = REG_RET; - emit_pre_pop_reg_flexible(emit, &vtype, ®, reg, reg); - ASM_NOT_REG(emit->as, reg); + ASM_NOT_REG(emit->as, REG_RET); + #else + #if REG_RET != REG_ARG_1 + int reg = REG_ARG_1; #else - int reg = REG_RET; - emit_pre_pop_reg_flexible(emit, &vtype, ®, REG_ARG_1, reg); - ASM_MOV_REG_IMM(emit->as, REG_ARG_1, -1); - ASM_XOR_REG_REG(emit->as, reg, REG_ARG_1); + int reg = REG_ARG_2; #endif - emit_post_push_reg(emit, vtype, reg); + ASM_MOV_REG_IMM(emit->as, reg, -1); + ASM_XOR_REG_REG(emit->as, REG_RET, reg); + #endif + emit_post_push_reg(emit, vtype, REG_RET); } else { EMIT_NATIVE_VIPER_TYPE_ERROR(emit, MP_ERROR_TEXT("'not' not implemented"), mp_binary_op_method_name[op]); @@ -2976,8 +2979,9 @@ static void emit_native_return_value(emit_t *emit) { static void emit_native_raise_varargs(emit_t *emit, mp_uint_t n_args) { DEBUG_printf("raise_varargs(%d)\n", n_args); - (void)n_args; - assert(n_args == 1); + if (n_args != 1) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("native raise")); + } vtype_kind_t vtype_exc; emit_pre_pop_reg(emit, &vtype_exc, REG_ARG_1); // arg1 = object to raise if (vtype_exc != VTYPE_PYOBJ) { diff --git a/py/emitndebug.c b/py/emitndebug.c index 2144d14e6b5..27c3bbdcef7 100644 --- a/py/emitndebug.c +++ b/py/emitndebug.c @@ -134,7 +134,7 @@ static void asm_debug_reg_imm(asm_debug_t *as, const char *op, int reg, int imm) #if !MICROPY_PERSISTENT_CODE_SAVE static void asm_debug_reg_qstr(asm_debug_t *as, const char *op, int reg, int qst) { - asm_debug_printf(as, "%s(%s, %s)\n", op, reg_name_table[reg], qstr_str(qst)); + asm_debug_printf(as, "%s(%s, %q)\n", op, reg_name_table[reg], (qstr)qst); } #endif diff --git a/py/gc.c b/py/gc.c index 38c169ccafd..57bad018e76 100644 --- a/py/gc.c +++ b/py/gc.c @@ -294,6 +294,10 @@ static void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) { #if MICROPY_GC_SPLIT_HEAP area->next = NULL; + + // Update the global min/max region that covers all heaps + MP_STATE_MEM(area_pool_min) = MIN(MP_STATE_MEM(area_pool_min), area->gc_pool_start); + MP_STATE_MEM(area_pool_max) = MAX(MP_STATE_MEM(area_pool_max), area->gc_pool_end); #endif DEBUG_printf("GC layout:\n"); @@ -329,6 +333,13 @@ void gc_init(void *start, void *end) { end = (void *)((uintptr_t)end & (~(BYTES_PER_BLOCK - 1))); DEBUG_printf("Initializing GC heap: %p..%p = " UINT_FMT " bytes\n", start, end, (byte *)end - (byte *)start); + #if MICROPY_GC_SPLIT_HEAP + // Note: min/max are deliberately swapped here, gc_setup_area() will update them to + // the correct values for the min/max of the first actual pool region + MP_STATE_MEM(area_pool_min) = end; + MP_STATE_MEM(area_pool_max) = start; + #endif + gc_setup_area(&MP_STATE_MEM(area), start, end); // set last free ATB index to start of heap @@ -529,12 +540,24 @@ bool gc_ptr_on_heap(const void *ptr) { } #if MICROPY_GC_SPLIT_HEAP -// Returns the area to which this pointer belongs, or NULL if it isn't -// allocated on the GC-managed heap. -static inline mp_state_mem_area_t *gc_get_ptr_area(const void *ptr) { - if (((uintptr_t)(ptr) & (BYTES_PER_BLOCK - 1)) != 0) { // must be aligned on a block - return NULL; +static mp_state_mem_area_t *gc_get_ptr_area(const void *ptr); + +// Returns the area to which this arbitrary pointer belongs, or NULL if it isn't +// allocated on the GC-managed heap. Contains "fast path" inline checks for invalid +// data which isn't a pointer to the heap. Equivalent of VERIFY_PTR for the non-split-heap case. +static inline MP_ALWAYSINLINE mp_state_mem_area_t *gc_verify_ptr_get_area(const void *ptr) { + // These inline checks are similar to VERIFY_PTR macro, below + if ((byte *)ptr < MP_STATE_MEM(area_pool_min) || (byte *)ptr > MP_STATE_MEM(area_pool_max)) { + return NULL; // not in the overall pool region + } + if (((uintptr_t)(ptr) & (BYTES_PER_BLOCK - 1)) != 0) { + return NULL; // not aligned on a block boundary } + return gc_get_ptr_area(ptr); +} + +// Returns the area to which a pointer belongs. Assumes pointer is valid to a heap block. +static mp_state_mem_area_t *gc_get_ptr_area(const void *ptr) { for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { if (ptr >= (void *)area->gc_pool_start // must be above start of pool && ptr < (void *)area->gc_pool_end) { // must be below end of pool @@ -543,7 +566,7 @@ static inline mp_state_mem_area_t *gc_get_ptr_area(const void *ptr) { } return NULL; } -#endif +#else // ptr should be of type void* #define VERIFY_PTR(ptr) ( \ @@ -552,6 +575,8 @@ static inline mp_state_mem_area_t *gc_get_ptr_area(const void *ptr) { && ptr < (void *)MP_STATE_MEM(area).gc_pool_end /* must be below end of pool */ \ ) +#endif + #ifdef TRACE_MARK #error "TRACE_MARK is replaced by TRACE_MARK_R and TRACE_MARK_S" #endif @@ -607,7 +632,7 @@ void gc_collect_root(void **ptrs, size_t len) { MICROPY_GC_HOOK_LOOP(i); void *ptr = gc_get_ptr(ptrs, i); #if MICROPY_GC_SPLIT_HEAP - mp_state_mem_area_t *area = gc_get_ptr_area(ptr); + mp_state_mem_area_t *area = gc_verify_ptr_get_area(ptr); if (!area) { continue; } @@ -676,7 +701,7 @@ static void MP_NO_INSTRUMENT PLACE_IN_ITCM(gc_mark_subtree)(size_t block) // If this is a heap pointer that hasn't been marked, mark it and push // it's children to the stack. #if MICROPY_GC_SPLIT_HEAP - mp_state_mem_area_t *ptr_area = gc_get_ptr_area(ptr); + mp_state_mem_area_t *ptr_area = gc_verify_ptr_get_area(ptr); if (!ptr_area) { // Not a heap-allocated pointer (might even be random data). continue; @@ -983,6 +1008,25 @@ void gc_info(gc_info_t *info) { GC_EXIT(); } +// Fast version of gc_info that only computes total/used/free. +void gc_info_fast(gc_info_t *info) { + GC_ENTER(); + memset(info, 0, sizeof(*info)); + const uint8_t lut[16] = {2, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}; + for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { + size_t free_blocks = 0; + info->total += area->gc_pool_end - area->gc_pool_start; + for (size_t i = 0; i < area->gc_alloc_table_byte_len; i++) { + uint8_t atb = area->gc_alloc_table_start[i]; + free_blocks += lut[atb & 0xF] + lut[atb >> 4]; + } + info->free += free_blocks; + } + info->free *= BYTES_PER_BLOCK; + info->used = info->total - info->free; + GC_EXIT(); +} + #if MICROPY_PY_WEAKREF // Mark the GC heap pointer as having a weakref. void gc_weakref_mark(void *ptr) { diff --git a/py/gc.h b/py/gc.h index f9028d637c5..0f693d49222 100644 --- a/py/gc.h +++ b/py/gc.h @@ -108,6 +108,7 @@ typedef struct _gc_info_t { } gc_info_t; void gc_info(gc_info_t *info); +void gc_info_fast(gc_info_t *info); void gc_dump_info(const mp_print_t *print); void gc_dump_alloc_table(const mp_print_t *print); diff --git a/py/makeqstrdata.py b/py/makeqstrdata.py index 3fdb2d5476b..b8613f2a1b3 100644 --- a/py/makeqstrdata.py +++ b/py/makeqstrdata.py @@ -434,7 +434,15 @@ def escape_bytes(qstr, qbytes): return "".join(("\\x%02x" % b) for b in qbytes) -def make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr): +# CIRCUITPY-CHANGE: the ROM pools store their strings in one NUL-separated blob per +# pool, addressed by 16-bit offsets. Each QDEF line carries the string's offset within +# its pool's blob as the fourth field. The blob is assembled by the C preprocessor +# concatenating the QDEF string literals in file order, so the offsets here must be +# the running total of (len + 1) in emission order for each pool. +QSTR_BLOB_MAX = 1 << 16 + + +def make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr, offset): qbytes = bytes_cons(qstr, "utf8") qlen = len(qbytes) qhash = compute_hash(qbytes, cfg_bytes_hash) @@ -442,7 +450,8 @@ def make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr): print("qstr is too long:", qstr) assert False qdata = escape_bytes(qstr, qbytes) - return '%d, %d, "%s"' % (qhash, qlen, qdata) + # CIRCUITPY-CHANGE: emit the blob offset; the caller advances it by qlen + 1 + return '%d, %d, %d, "%s"' % (qhash, qlen, offset, qdata), qlen + 1 # CIRCUITPY-CHANGE: add translations @@ -455,12 +464,19 @@ def print_qstr_data(qcfgs, qstrs, translations): print("// This file was automatically generated by makeqstrdata.py") print("") + # CIRCUITPY-CHANGE: running blob offset for each ROM pool + offsets = [0, 0] + # add NULL qstr with no hash or data - print('QDEF0(MP_QSTRnull, 0, 0, "")') + # CIRCUITPY-CHANGE: offset field; the null qstr occupies one NUL byte of the blob + print('QDEF0(MP_QSTRnull, 0, 0, 0, "")') + offsets[0] += 1 # add static qstrs to the first unsorted pool for qstr in static_qstr_list: - qbytes = make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr) + # CIRCUITPY-CHANGE: pass and advance the blob offset + qbytes, size = make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr, offsets[0]) + offsets[0] += size print("QDEF0(MP_QSTR_%s, %s)" % (qstr_escape(qstr), qbytes)) # CIRCUITPY-CHANGE: track total qstr size @@ -469,13 +485,22 @@ def print_qstr_data(qcfgs, qstrs, translations): # add remaining qstrs to the sorted (by value) pool (unless they're in # unsorted_qstr_list, in which case add them to the unsorted pool) for ident, qstr in sorted(qstrs.values(), key=lambda x: x[1]): - qbytes = make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr) pool = 0 if qstr in unsorted_qstr_list else 1 + # CIRCUITPY-CHANGE: pass and advance the blob offset + qbytes, size = make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr, offsets[pool]) + offsets[pool] += size print("QDEF%d(MP_QSTR_%s, %s)" % (pool, ident, qbytes)) # CIRCUITPY-CHANGE: track total qstr size total_qstr_size += len(qstr) + # CIRCUITPY-CHANGE: offsets are stored as uint16_t (qstr_offset_t in py/qstr.h), + # including the trailing end-of-blob entry + for pool, total in enumerate(offsets): + if total >= QSTR_BLOB_MAX: + print("qstr blob for pool %d is too large: %d bytes" % (pool, total), file=sys.stderr) + assert False + # CIRCUITPY-CHANGE: translations print( "// Enumerate translated texts but don't actually include translations. Instead, the linker will link them in." diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index 8c07899baf8..3bc81bf5961 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -94,10 +94,36 @@ def preprocess(): except OSError: pass + # These regex's are used to filter the preprocessed data, keeping only those lines + # that are subsequently needed by the `process_file` step. The regexs are kept + # short so they are as efficient as possible. (The stm32 port needs symbols of the + # form `micropy_hw_xxx` so they are also kept.) + re_line_file = re.compile(rb"^#(?:line)?\s+\d+\s\"") + re_mp_info = re.compile(rb"MP_COMP|MP_QSTR|MP_REGI|micropy_hw") + def pp(flags): def run(files): try: - return subprocess.check_output(args.pp + flags + files) + filtered_lines = [] + cmd = args.pp + flags + files + with subprocess.Popen(cmd, stdout=subprocess.PIPE) as proc: + for line in proc.stdout: + if line.isspace(): + pass + elif re_line_file.match(line): + # CIRCUITPY-CHANGE: keep every file marker, not just + # the one preceding an MP symbol. CircuitPython + # attributes MP_COMPRESSED_ROM_TEXT found in headers + # to the enclosing C source, so process_file needs + # the C source markers even when the symbol itself + # comes from a header. + filtered_lines.append(line) + elif re_mp_info.search(line): + filtered_lines.append(line) + proc.wait() + if proc.returncode: + raise PreprocessorError("command failed: " + " ".join(cmd)) + return b"".join(filtered_lines) except subprocess.CalledProcessError as er: raise PreprocessorError(str(er)) diff --git a/py/maketranslationdata.py b/py/maketranslationdata.py index c3c3442e311..14bd7a25e89 100644 --- a/py/maketranslationdata.py +++ b/py/maketranslationdata.py @@ -6,13 +6,13 @@ supported CPython version are unintended. For documentation about the format of compressed translated strings, see -supervisor/shared/translate/translate.h +supervisor/shared/translate/compressed_string.h """ from __future__ import print_function import bisect -from dataclasses import dataclass +from dataclasses import dataclass, field import re import sys @@ -73,6 +73,102 @@ '"': '\\"', } +# Symbol values with a special meaning, as characters. Must match +# translation_symbol_t in supervisor/shared/translate/compressed_string.h. +SYMBOL_QSTR = "\1" +SYMBOL_RARE_INDEX = "\2" +SYMBOL_RARE_RAW = "\3" +RESERVED_CHARS = {SYMBOL_QSTR, SYMBOL_RARE_INDEX, SYMBOL_RARE_RAW} + +# The first non-ASCII symbol value in the dense alphabet. +ALPHABET_BASE = 0x80 + +# Huffman codes are chosen per "class" of the previously decoded character. +# These base classes are fixed in the C decoder (translate.c, base_class()) and +# must match translation_class_t in supervisor/shared/translate/compressed_string.h; +# the generator collapses them with class_map[] into TRANSLATION_CLASSES tables. +CLASS_START_OR_SPACE = 0 +CLASS_LOWER = 1 +CLASS_UPPER = 2 +CLASS_DIGIT = 3 +CLASS_OTHER = 4 +CLASS_PERCENT = 5 +CLASS_NON_ASCII = 6 +NUM_BASE_CLASSES = 7 +# Presets to try. Each is the list of Huffman tables to build, each table +# naming the base classes that share it. class_map_for() turns one into the +# class_map[] the decoder uses. +CLASS_PRESETS = ( + # one table: the tables cost more than they save + [ + [ + CLASS_START_OR_SPACE, + CLASS_LOWER, + CLASS_UPPER, + CLASS_DIGIT, + CLASS_OTHER, + CLASS_PERCENT, + CLASS_NON_ASCII, + ] + ], + # space, letter, other + [ + [CLASS_START_OR_SPACE], + [CLASS_LOWER, CLASS_UPPER, CLASS_NON_ASCII], + [CLASS_DIGIT, CLASS_OTHER, CLASS_PERCENT], + ], + # non-ASCII shares the lowercase table + [ + [CLASS_START_OR_SPACE], + [CLASS_LOWER, CLASS_NON_ASCII], + [CLASS_UPPER], + [CLASS_DIGIT], + [CLASS_OTHER], + [CLASS_PERCENT], + ], + # non-ASCII has its own table + [ + [CLASS_START_OR_SPACE], + [CLASS_LOWER], + [CLASS_UPPER], + [CLASS_DIGIT], + [CLASS_OTHER], + [CLASS_PERCENT], + [CLASS_NON_ASCII], + ], +) + + +def class_map_for(preset): + """class_map[] for a preset: base class -> index of the table it uses.""" + class_map = [None] * NUM_BASE_CLASSES + for table, classes in enumerate(preset): + for cls in classes: + assert class_map[cls] is None, f"class {cls} in two tables" + class_map[cls] = table + assert None not in class_map, "every base class needs a table" + return tuple(class_map) + + +def base_class(c): + """Class of the character preceding the next symbol; None means start of string. + Computed on the (possibly remapped) character: remapped characters are >= 0x80 + exactly when the original is, which is all the classification looks at.""" + if c is None or c == " ": + return CLASS_START_OR_SPACE + o = ord(c) + if 0x61 <= o <= 0x7A: + return CLASS_LOWER + if 0x41 <= o <= 0x5A: + return CLASS_UPPER + if 0x30 <= o <= 0x39: + return CLASS_DIGIT + if o == 0x25: # '%' + return CLASS_PERCENT + if o >= 0x80: + return CLASS_NON_ASCII + return CLASS_OTHER + # this must match the equivalent function in qstr.c def compute_hash(qstr, bytes_hash): @@ -140,100 +236,183 @@ def iter_substrings(s, minlen, maxlen): yield s[begin : begin + n] -translation_requires_uint16 = {"cs", "ja", "ko", "pl", "tr", "zh_Latn_pinyin"} +# Non-ASCII characters that do not get a dense symbol are "rare". While the text +# is being processed they are remapped into two private-use ranges so that they +# stay single characters: those used more than once become an index into +# rare_chars[], those used once are coded as a raw 16-bit code point. +RARE_IDX_BASE = 0xF0000 +RARE_RAW_BASE = 0x100000 +RARE_SPLIT = re.compile("[\U000f0000-\U0010ffff]") +# Dense alphabet sizes to try when a language has more non-ASCII characters +# than fit; the rest of the code points go to the dictionary. +DENSE_ALPHABET_TRIES = (32, 48, 64, 80) -def compute_unicode_offset(texts): - all_ch = set(" ".join(texts)) - ch_160 = sorted(c for c in all_ch if 160 <= ord(c) < 255) - ch_256 = sorted(c for c in all_ch if 255 < ord(c)) - if not ch_256: - return 0, 0 - min_256 = ord(min(ch_256)) - span = ord(max(ch_256)) - ord(min(ch_256)) + 1 +@dataclass +class EncodingTable: + # One entry per class table: list of symbols in canonical order. + values: list + # One entry per class table: list of code-length counts, padded to lengths_row. + lengths: list + lengths_row: int + class_map: tuple + # Per class table: symbol -> canonical code (string of '0'/'1'). + canonical: list + # Dictionary words, in remapped characters, sorted by length. + words: list + word_start: int + # Dense alphabet: index -> original character. + alphabet: list + # Rare characters coded by index -> original character. + rare_idx_chars: list + rare_index_bits: int + # Original character -> remapped character (identity for ASCII). + remap: dict + translation_qstr_bits: int + qstrs: dict + qstrs_inv: dict + # Remapped text -> token list used to encode it. A token is a str (character + # or word) or a ("q", qstr) tuple. + tokens: dict = field(default_factory=dict) - if ch_160: - max_160 = ord(max(ch_160)) + 1 - else: - max_160 = max(160, 255 - span) - if max_160 + span > 256: - return 0, 0 +def remap_text(text, remap): + return "".join(remap.get(c, c) for c in text) - offstart = max_160 - offset = min_256 - max_160 - return offstart, offset +def is_qstr(token): + return isinstance(token, tuple) -@dataclass -class EncodingTable: - values: object - lengths: object - words: object - canonical: object - extractor: object - apply_offset: object - remove_offset: object - translation_qstr_bits: int - qstrs: object - qstrs_inv: object +def is_rare(token): + return len(token) == 1 and ord(token) >= RARE_IDX_BASE -def compute_huffman_coding(qstrs, translation_name, translations, f, compression_level): - # possible future improvement: some languages are better when consider len(k) > 2. try both? - qstrs = dict((k, v) for k, v in qstrs.items() if len(k) > 3) - qstr_strs = list(qstrs.keys()) - texts = [t[1] for t in translations] - words = [] - start_unused = 0x80 - end_unused = 0xFF - max_ord = 0 - offstart, offset = compute_unicode_offset(texts) - - def apply_offset(c): - oc = ord(c) - if oc >= offstart: - oc += offset - return chr(oc) - - def remove_offset(c): - oc = ord(c) - if oc >= offstart: - oc = oc - offset - try: - return chr(oc) - except Exception as e: - raise ValueError(f"remove_offset {offstart=} {oc=}") from e +def token_len(token): + return len(token[1]) if is_qstr(token) else len(token) + + +def token_symbol(token): + """The Huffman symbol a token is coded as. qstrs and rare characters are + coded as an escape symbol followed by a fixed number of bits.""" + if is_qstr(token): + return SYMBOL_QSTR + if is_rare(token): + return SYMBOL_RARE_RAW if ord(token) >= RARE_RAW_BASE else SYMBOL_RARE_INDEX + return token - for text in texts: - for c in text: - c = remove_offset(c) - ord_c = ord(c) - max_ord = max(ord_c, max_ord) - if 0x80 <= ord_c < 0xFF: - end_unused = min(ord_c, end_unused) - max_words = end_unused - 0x80 - if compression_level < 5: - max_words = 0 - - bits_per_codepoint = 16 if max_ord > 255 else 8 - values_type = "uint16_t" if max_ord > 255 else "uint8_t" - translation_name = translation_name.split("/")[-1].split(".")[0] - if max_ord > 255 and translation_name not in translation_requires_uint16: - raise ValueError( - f"Translation {translation_name} expected to fit in 8 bits but required 16 bits" - ) - # Prune the qstrs to only those that appear in the texts - qstr_counters = collections.Counter() - qstr_extractor = TextSplitter(qstr_strs) - for t in texts: - for qstr in qstr_extractor.iter(t): - if qstr in qstr_strs: - qstr_counters[qstr] += 1 - qstr_strs = list(qstr_counters.keys()) +def token_extra_bits(token, qstr_bits, rare_index_bits): + """Fixed-width bits that follow the token's Huffman symbol.""" + if is_qstr(token): + return qstr_bits + if is_rare(token): + return 16 if ord(token) >= RARE_RAW_BASE else rare_index_bits + return 0 + +def code_lengths(counter): + """Huffman code lengths for a symbol counter. A lone symbol gets a 1-bit code.""" + if len(counter) == 1: + return {next(iter(counter)): 1} + cb = huffman.codebook(counter.items()) + return {k: len(v) for k, v in cb.items()} + + +def canonical_codes(lengths): + """Canonical Huffman codes from a symbol -> length dict. + Returns (values in canonical order, per-length counts, symbol -> code).""" + if not lengths: + return [], [], {} + values = [] + length_count = collections.Counter() + canonical = {} + renumbered = 0 + last_length = None + for atom, length in sorted(lengths.items(), key=lambda x: (x[1], x[0])): + values.append(atom) + length_count[length] += 1 + if last_length: + renumbered <<= length - last_length + canonical[atom] = "{0:0{width}b}".format(renumbered, width=length) + renumbered += 1 + last_length = length + counts = [length_count.get(i, 0) for i in range(1, max(length_count) + 2)] + return values, counts, canonical + + +def parse_optimal( + text, + lens, + class_map, + words_by_first, + qstrs_by_first, + qstr_bits, + rare_index_bits, + unknown_len=32, +): + """Tokenize text with the fewest bits given per-class code lengths. + Symbols missing from a class table are allowed at a penalty so a parse always exists.""" + n = len(text) + best = [math.inf] * (n + 1) + back = [None] * (n + 1) + best[0] = 0 + for i in range(n): + if best[i] == math.inf: + continue + table = lens[class_map[base_class(text[i - 1] if i else None)]] + c = text[i] + cands = [c] + for w in words_by_first.get(c, ()): + if text.startswith(w, i): + cands.append(w) + for q in qstrs_by_first.get(c, ()): + if text.startswith(q, i): + cands.append(("q", q)) + for tok in cands: + cost = table.get(token_symbol(tok), unknown_len) + cost += token_extra_bits(tok, qstr_bits, rare_index_bits) + j = i + token_len(tok) + if best[i] + cost < best[j]: + best[j] = best[i] + cost + back[j] = tok + out = [] + i = n + while i > 0: + tok = back[i] + out.append(tok) + i -= token_len(tok) + out.reverse() + return out + + +def tally(texts, tokens, class_map): + """Symbol counts per class table for the given tokenizations.""" + counts = collections.defaultdict(collections.Counter) + for text in texts: + pos = 0 + for tok in tokens[text]: + cls = class_map[base_class(text[pos - 1] if pos else None)] + counts[cls][token_symbol(tok)] += 1 + pos += token_len(tok) + return counts + + +def encoded_bits(text, tokens, lens, class_map, qstr_bits, rare_index_bits): + bits = 0 + pos = 0 + for tok in tokens: + cls = class_map[base_class(text[pos - 1] if pos else None)] + bits += lens[cls][token_symbol(tok)] + bits += token_extra_bits(tok, qstr_bits, rare_index_bits) + pos += token_len(tok) + return bits + + +def find_words(texts, qstrs, qstr_strs, max_words): + """Greedy dictionary search: repeatedly add the 2- to 11-gram estimated to + save the most bits until the dictionary is full or nothing pays off.""" + words = [] while len(words) < max_words: # Until the dictionary is filled to capacity, use a heuristic to find # the best "word" (2- to 11-gram) to add to it. @@ -248,7 +427,7 @@ def remove_offset(c): for t in texts: for atom in extractor.iter(t): if atom in qstrs: - atom = "\1" + atom = SYMBOL_QSTR counter[atom] += 1 cb = huffman.codebook(counter.items()) lengths = sorted(dict((v, len(cb[k])) for k, v in counter.items()).items()) @@ -284,15 +463,18 @@ def est_len(occ): # The difference between the two is the estimated net savings, in bits. def est_net_savings(s, occ): savings = occ * (bit_length(s) - est_len(occ)) - cost = len(s) * bits_per_codepoint + 24 + cost = len(s) * 8 + 24 return savings - cost counter = collections.Counter() for t in texts: for found, word in extractor.iter_words(t): if not found: - for substr in iter_substrings(word, minlen=2, maxlen=11): - counter[substr] += 1 + # Words are stored as 8-bit symbols, so they cannot contain + # rare (escaped) characters: split around them. + for piece in RARE_SPLIT.split(word): + for substr in iter_substrings(piece, minlen=2, maxlen=11): + counter[substr] += 1 # Score the candidates we found. This is a semi-empirical formula that # attempts to model the number of bits saved as closely as possible. @@ -319,90 +501,239 @@ def est_net_savings(s, occ): word = scores[0][0] words.append(word) + return words + - splitters = words[:] - if compression_level > 3: - splitters.extend(qstr_strs) +def compute_huffman_coding(qstrs, translation_name, translations, f, compression_level): + # possible future improvement: some languages are better when consider len(k) > 2. try both? + qstrs = dict((k, v) for k, v in qstrs.items() if len(k) > 3) + all_qstr_strs = list(qstrs.keys()) + original_texts = [t[1] for t in translations] + + for text in original_texts: + bad = RESERVED_CHARS.intersection(text) + if bad: + raise ValueError(f"Translation contains reserved character {bad!r}: {text!r}") + if any(ord(c) >= RARE_IDX_BASE for c in text): + raise ValueError(f"Translation contains private-use character: {text!r}") + + max_translation_encoded_length = max(len(t.encode("utf-8")) for t in original_texts) + encoded_length_bits = max_translation_encoded_length.bit_length() + use_qstrs = compression_level > 3 + + # Non-ASCII characters by decreasing frequency. The most frequent ones are + # renumbered from 0x80 (the dense alphabet), so that all symbols fit in 8 + # bits; the rest are escaped. + hi_count = collections.Counter(c for t in original_texts for c in t if ord(c) >= 0x80) + hi_chars = [c for c, _ in hi_count.most_common()] + if len(hi_chars) <= 0x7F: + dense_tries = (len(hi_chars),) + else: + dense_tries = DENSE_ALPHABET_TRIES + + def encode(dense_count): + alphabet = hi_chars[:dense_count] + rare = hi_chars[dense_count:] + rare_idx_chars = [c for c in rare if hi_count[c] > 1] + rare_raw_chars = [c for c in rare if hi_count[c] == 1] + rare_index_bits = (len(rare_idx_chars) - 1).bit_length() if rare_idx_chars else 0 + remap = {c: chr(ALPHABET_BASE + i) for i, c in enumerate(alphabet)} + remap.update({c: chr(RARE_IDX_BASE + i) for i, c in enumerate(rare_idx_chars)}) + remap.update({c: chr(RARE_RAW_BASE + i) for i, c in enumerate(rare_raw_chars)}) + word_start = ALPHABET_BASE + len(alphabet) + max_words = 0x100 - word_start if compression_level >= 5 else 0 + + texts = [remap_text(t, remap) for t in original_texts] + + # Prune the qstrs to only those that appear in the texts + qstr_counters = collections.Counter() + qstr_extractor = TextSplitter(all_qstr_strs) + for t in texts: + for qstr in qstr_extractor.iter(t): + if qstr in qstrs: + qstr_counters[qstr] += 1 + qstr_strs = list(qstr_counters.keys()) + + words = find_words(texts, qstrs, qstr_strs, max_words) + + # Now that the dictionary is fixed, find the tokenization of each string + # that takes the fewest bits, with Huffman codes chosen per class of the + # previous character. Code lengths and tokenization depend on each other, + # so iterate a few rounds from the greedy tokenization; the final + # tokenization and the codes built from it are what get emitted, so they + # are always consistent. + dp_qstrs = qstr_strs if use_qstrs else [] + # Upper bound on the qstr index width while parsing; the real width is computed below. + est_qstr_bits = max([0] + [qstrs[q] for q in dp_qstrs]).bit_length() + words_by_first = collections.defaultdict(list) + for w in words: + words_by_first[w[0]].append(w) + qstrs_by_first = collections.defaultdict(list) + for q in dp_qstrs: + qstrs_by_first[q[0]].append(q) + + greedy = TextSplitter(words + dp_qstrs) + greedy_tokens = {} + for t in texts: + greedy_tokens[t] = [("q", a) if a in qstrs else a for a in greedy.iter(t)] + + def table_bytes(class_map, counts, used_words): + nclasses = max(class_map) + 1 + row = max(max(code_lengths(cn).values()) for cn in counts.values()) + 1 + n = sum(len(cn) for cn in counts.values()) # values + n += nclasses * row # lengths + n += 2 * (nclasses + 1) # values_offset + n += NUM_BASE_CLASSES # class_map + n += sum(len(w) for w in used_words) # words + if used_words: + n += len(used_words[-1]) - len(used_words[0]) + 1 # wlencount + n += 2 * len(alphabet) + n += 2 * len(rare_idx_chars) + return n + + best = None + for preset in CLASS_PRESETS: + class_map = class_map_for(preset) + tokens = greedy_tokens + counts = tally(texts, tokens, class_map) + lens = {cls: code_lengths(cn) for cls, cn in counts.items()} + for _ in range(3): + tokens = { + t: parse_optimal( + t, + lens, + class_map, + words_by_first, + qstrs_by_first, + est_qstr_bits, + rare_index_bits, + ) + for t in texts + } + counts = tally(texts, tokens, class_map) + lens = {cls: code_lengths(cn) for cls, cn in counts.items()} + used_symbols = set().union(*counts.values()) + used_words = sorted((w for w in words if w in used_symbols), key=len) + size = table_bytes(class_map, counts, used_words) + sum( + ( + encoded_length_bits + + encoded_bits(t, tokens[t], lens, class_map, est_qstr_bits, rare_index_bits) + + 7 + ) + // 8 + for t in texts + ) + if best is None or size < best[0]: + best = (size, class_map, tokens, lens, used_words) + size, class_map, tokens, lens, words = best + return size, dict( + class_map=class_map, + tokens=tokens, + lens=lens, + words=words, + alphabet=alphabet, + rare_idx_chars=rare_idx_chars, + rare_raw_chars=rare_raw_chars, + rare_index_bits=rare_index_bits, + remap=remap, + word_start=word_start, + ) + + size, r = min((encode(k) for k in dense_tries), key=lambda x: x[0]) + class_map = r["class_map"] + tokens = r["tokens"] + lens = r["lens"] + words = r["words"] + alphabet = r["alphabet"] + rare_idx_chars = r["rare_idx_chars"] + rare_raw_chars = r["rare_raw_chars"] + rare_index_bits = r["rare_index_bits"] + remap = r["remap"] + word_start = r["word_start"] - words.sort(key=len) - extractor = TextSplitter(splitters) - counter = collections.Counter() used_qstr = 0 - for t in texts: - for atom in extractor.iter(t): - if atom in qstrs: - used_qstr = max(used_qstr, qstrs[atom]) - atom = "\1" - counter[atom] += 1 - cb = huffman.codebook(counter.items()) + for toks in tokens.values(): + for tok in toks: + if is_qstr(tok): + used_qstr = max(used_qstr, qstrs[tok[1]]) + translation_qstr_bits = used_qstr.bit_length() - word_start = start_unused word_end = word_start + len(words) - 1 - f.write(f"// # words {len(words)}\n") - f.write(f"// words {words}\n") + word_code = {w: chr(word_start + i) for i, w in enumerate(words)} - values = [] - length_count = {} - renumbered = 0 - last_length = None - canonical = {} - for atom, code in sorted(cb.items(), key=lambda x: (len(x[1]), x[0])): - if atom in qstr_strs: - atom = "\1" - values.append(atom) - length = len(code) - if length not in length_count: - length_count[length] = 0 - length_count[length] += 1 - if last_length: - renumbered <<= length - last_length - # print(f"atom={repr(atom)} code={code}", file=sys.stderr) - canonical[atom] = "{0:0{width}b}".format(renumbered, width=length) - if len(atom) > 1: - o = words.index(atom) + 0x80 - s = "".join(C_ESCAPES.get(ch1, ch1) for ch1 in atom) - f.write(f"// {o} {s} {counter[atom]} {canonical[atom]} {renumbered}\n") - else: - s = C_ESCAPES.get(atom, atom) - canonical[atom] = "{0:0{width}b}".format(renumbered, width=length) - o = ord(atom) - f.write(f"// {o} {s} {counter[atom]} {canonical[atom]} {renumbered}\n") - renumbered += 1 - last_length = length - lengths = bytearray() - f.write(f"// length count {length_count}\n") - - for i in range(1, max(length_count) + 2): - lengths.append(length_count.get(i, 0)) - f.write(f"// values {values} lengths {len(lengths)} {lengths}\n") + def symbol_value(sym): + return ord(word_code.get(sym, sym)) - f.write(f"// {values} {lengths}\n") - values = [(atom if len(atom) == 1 else chr(0x80 + words.index(atom))) for atom in values] - max_translation_encoded_length = max( - len(translation.encode("utf-8")) for (original, translation) in translations - ) + nclasses = max(class_map) + 1 + values = [] + lengths_rows = [] + canonical = [] + for cls in range(nclasses): + # Symbols are ordered by (length, value) so that the C decoder's canonical + # walk lands on the same index. + table = lens.get(cls, {}) + by_value = {chr(symbol_value(s)): l for s, l in table.items()} + v, counts_row, canon = canonical_codes(by_value) + values.append([ord(x) for x in v]) + lengths_rows.append(counts_row) + canonical.append({s: canon[chr(symbol_value(s))] for s in table}) + lengths_row = max(1, max(len(r) for r in lengths_rows)) + lengths_rows = [r + [0] * (lengths_row - len(r)) for r in lengths_rows] + assert all(len(code) >= 1 for canon in canonical for code in canon.values()) + assert all(v < 0x100 for vs in values for v in vs) + + unmap = {v: k for k, v in remap.items()} + f.write(f"// # words {len(words)}\n") + f.write("// words {}\n".format([remap_text(w, unmap) for w in words])) + f.write(f"// # alphabet {len(alphabet)}\n") + f.write(f"// # rare chars by index {len(rare_idx_chars)}, raw {len(rare_raw_chars)}\n") + f.write(f"// class_map {class_map} tables {nclasses}\n") + for cls in range(nclasses): + f.write(f"// class {cls}: {len(values[cls])} symbols, lengths {lengths_rows[cls]}\n") maxlen = len(words[-1]) if words else 0 minlen = len(words[0]) if words else 0 wlencount = [len([None for w in words if len(w) == l]) for l in range(minlen, maxlen + 1)] - translation_qstr_bits = used_qstr.bit_length() - - f.write("typedef {} mchar_t;\n".format(values_type)) - f.write("const uint8_t lengths[] = {{ {} }};\n".format(", ".join(map(str, lengths)))) + f.write("#define compress_max_length_bits ({})\n".format(encoded_length_bits)) + f.write("#define TRANSLATION_CLASSES {}\n".format(nclasses)) + f.write("#define LENGTHS_ROW {}\n".format(lengths_row)) f.write( - "const mchar_t values[] = {{ {} }};\n".format( - ", ".join(str(ord(remove_offset(u))) for u in values) + "const uint8_t class_map[{}] = {{ {} }};\n".format( + NUM_BASE_CLASSES, ", ".join(map(str, class_map)) ) ) f.write( - "#define compress_max_length_bits ({})\n".format( - max_translation_encoded_length.bit_length() + "const uint8_t lengths[] = {{ {} }};\n".format( + ", ".join(str(x) for row in lengths_rows for x in row) ) ) + offsets = [0] + for v in values: + offsets.append(offsets[-1] + len(v)) f.write( - "const mchar_t words[] = {{ {} }};\n".format( - ", ".join(str(ord(remove_offset(c))) for w in words for c in w) + "const uint16_t values_offset[{}] = {{ {} }};\n".format( + nclasses + 1, ", ".join(map(str, offsets)) + ) + ) + f.write( + "const uint8_t values[] = {{ {} }};\n".format(", ".join(str(x) for v in values for x in v)) + ) + f.write("#define alphabet_size {}\n".format(len(alphabet))) + f.write( + "const uint16_t alphabet[] = {{ {} }};\n".format(", ".join(str(ord(c)) for c in alphabet)) + ) + f.write("#define rare_index_count {}\n".format(len(rare_idx_chars))) + f.write("#define rare_index_bits {}\n".format(rare_index_bits)) + f.write("#define rare_raw_count {}\n".format(len(rare_raw_chars))) + f.write( + "const uint16_t rare_chars[] = {{ {} }};\n".format( + ", ".join(str(ord(c)) for c in rare_idx_chars) + ) + ) + f.write( + "const uint8_t words[] = {{ {} }};\n".format( + ", ".join(str(ord(c)) for w in words for c in w) ) ) f.write("const uint8_t wlencount[] = {{ {} }};\n".format(", ".join(str(p) for p in wlencount))) @@ -410,30 +741,33 @@ def est_net_savings(s, occ): f.write("#define word_end {}\n".format(word_end)) f.write("#define minlen {}\n".format(minlen)) f.write("#define maxlen {}\n".format(maxlen)) - f.write("#define translation_offstart {}\n".format(offstart)) - f.write("#define translation_offset {}\n".format(offset)) f.write("#define translation_qstr_bits {}\n".format(translation_qstr_bits)) qstrs_inv = dict((v, k) for k, v in qstrs.items()) return EncodingTable( values, - lengths, - words, + lengths_rows, + lengths_row, + class_map, canonical, - extractor, - apply_offset, - remove_offset, + words, + word_start, + alphabet, + rare_idx_chars, + rare_index_bits, + remap, translation_qstr_bits, qstrs, qstrs_inv, + tokens, ) def decompress(encoding_table, encoded, encoded_length_bits): - qstrs_inv = encoding_table.qstrs_inv - values = encoding_table.values - lengths = encoding_table.lengths - words = encoding_table.words + """Decode as the C decoder does (translate.c). Returns the original text.""" + et = encoding_table + alphabet_size = len(et.alphabet) + word_end = et.word_start + len(et.words) - 1 def bititer(): for byte in encoded: @@ -449,10 +783,25 @@ def getnbits(n): return bits dec = [] + last = None length = getnbits(encoded_length_bits) + decoded = 0 - i = 0 - while i < length: + def emit_char(c): + nonlocal last, decoded + dec.append(c) + decoded += len(c.encode("utf-8")) + last = c[-1] + + def emit(u): + if ALPHABET_BASE <= u < ALPHABET_BASE + alphabet_size: + emit_char(et.alphabet[u - ALPHABET_BASE]) + else: + emit_char(chr(u)) + + while decoded < length: + cls = et.class_map[base_class(last)] + lengths = et.lengths[cls] bits = 0 bit_length = 0 max_code = lengths[0] @@ -461,28 +810,33 @@ def getnbits(n): bits = (bits << 1) | nextbit() bit_length += 1 if max_code > 0 and bits < max_code: - # print('{0:0{width}b}'.format(bits, width=bit_length)) break max_code = (max_code << 1) + lengths[bit_length] searched_length += lengths[bit_length] - - v = values[searched_length + bits - max_code] - if v == chr(1): - qstr_idx = getnbits(encoding_table.translation_qstr_bits) - v = qstrs_inv[qstr_idx] - elif v >= chr(0x80) and v < chr(0x80 + len(words)): - v = words[ord(v) - 0x80] - i += len(v.encode("utf-8")) - dec.append(v) + v = et.values[cls][searched_length + bits - max_code] + if v == 1: + qstr_idx = getnbits(et.translation_qstr_bits) + emit_char(et.qstrs_inv[qstr_idx]) + elif v == 2: + emit_char(et.rare_idx_chars[getnbits(et.rare_index_bits)]) + elif v == 3: + emit_char(chr(getnbits(16))) + elif et.word_start <= v <= word_end: + for c in et.words[v - et.word_start]: + emit(ord(c)) + else: + emit(v) return "".join(dec) def compress(encoding_table, decompressed, encoded_length_bits, len_translation_encoded): + """Encode a translation using the tokenization chosen in compute_huffman_coding().""" if not isinstance(decompressed, str): raise TypeError() - qstrs = encoding_table.qstrs - canonical = encoding_table.canonical - extractor = encoding_table.extractor + et = encoding_table + text = remap_text(decompressed, et.remap) + tokens = et.tokens[text] + unmap = {v: k for k, v in et.remap.items()} enc = 1 @@ -494,17 +848,25 @@ def put_bits(enc, b, n): enc = put_bit(enc, b & (1 << i)) return enc + def put_code(enc, cls, sym): + for b in et.canonical[cls][sym]: + enc = put_bit(enc, b == "1") + return enc + enc = put_bits(enc, len_translation_encoded, encoded_length_bits) - for atom in extractor.iter(decompressed): - if atom in qstrs: - can = canonical["\1"] - else: - can = canonical[atom] - for b in can: - enc = put_bit(enc, b == "1") - if atom in qstrs: - enc = put_bits(enc, qstrs[atom], encoding_table.translation_qstr_bits) + pos = 0 + for tok in tokens: + cls = et.class_map[base_class(text[pos - 1] if pos else None)] + enc = put_code(enc, cls, token_symbol(tok)) + if is_qstr(tok): + enc = put_bits(enc, et.qstrs[tok[1]], et.translation_qstr_bits) + elif is_rare(tok): + if ord(tok) >= RARE_RAW_BASE: + enc = put_bits(enc, ord(unmap[tok]), 16) + else: + enc = put_bits(enc, ord(tok) - RARE_IDX_BASE, et.rare_index_bits) + pos += token_len(tok) while enc.bit_length() % 8 != 1: enc = put_bit(enc, 0) @@ -528,7 +890,8 @@ def esc_char(m): def parse_qstrs(infile): r = {} rx = re.compile( - r'QDEF(?P[01])\([A-Za-z0-9_]+,\s*\d+,\s*\d+,\s*(?P"(?:[^"\\\\]|\\.)*")\)' + # QDEFn(ident, hash, len, blob_offset, "str") + r'QDEF(?P[01])\([A-Za-z0-9_]+,\s*\d+,\s*\d+,\s*\d+,\s*(?P"(?:[^"\\\\]|\\.)*")\)' ) content = infile.read() matches = rx.finditer(content) diff --git a/py/malloc.c b/py/malloc.c index fc9795043a8..5882a463323 100644 --- a/py/malloc.c +++ b/py/malloc.c @@ -273,6 +273,15 @@ typedef struct _m_tracked_node_t { uint8_t data[]; } m_tracked_node_t; +// Helper to get data size of a tracked node, abstracting MICROPY_TRACKED_ALLOC_STORE_SIZE. +static inline size_t get_tracked_node_size(m_tracked_node_t *node) { + #if MICROPY_TRACKED_ALLOC_STORE_SIZE + return node->size; + #else + return gc_nbytes(node) - sizeof(m_tracked_node_t); + #endif +} + #if MICROPY_DEBUG_VERBOSE static size_t m_tracked_count_links(size_t *nb) { m_tracked_node_lock(); @@ -320,6 +329,42 @@ void *m_tracked_calloc(size_t nmemb, size_t size) { return &node->data[0]; } +void *m_tracked_realloc(void *ptr_in, size_t n_bytes) { + // Handle pure allocation + if (ptr_in == NULL) { + return m_tracked_calloc(1, n_bytes); + } + + // Handle pure free + if (n_bytes == 0) { + m_tracked_free(ptr_in); + return NULL; + } + // To keep the implementation simple, we always allocate a new buffer and copy the old data into it. + // This could be optimised if faster performance or lower worst-case memory usage is required. + + // Get old size + // CIRCUITPY-CHANGE: cast to avoid compiler warning + m_tracked_node_t *old_node = (m_tracked_node_t *)(void *)((uint8_t *)ptr_in - sizeof(m_tracked_node_t)); + size_t old_size = get_tracked_node_size(old_node); + + // Allocate new buffer + void *new_ptr = m_tracked_calloc(1, n_bytes); + if (new_ptr == NULL) { + // Allocation failed, return NULL but leave original pointer intact + return NULL; + } + + // Copy data (minimum of old and new size) + size_t copy_size = MIN(old_size, n_bytes); + memcpy(new_ptr, ptr_in, copy_size); + + // Free old buffer + m_tracked_free(ptr_in); + + return new_ptr; +} + void m_tracked_free(void *ptr_in) { if (ptr_in == NULL) { return; @@ -328,11 +373,7 @@ void m_tracked_free(void *ptr_in) { m_tracked_node_t *node = (m_tracked_node_t *)(void *)((uint8_t *)ptr_in - sizeof(m_tracked_node_t)); #if MICROPY_DEBUG_VERBOSE size_t data_bytes; - #if MICROPY_TRACKED_ALLOC_STORE_SIZE - data_bytes = node->size; - #else - data_bytes = gc_nbytes(node); - #endif + data_bytes = get_tracked_node_size(node); size_t nb; size_t n = m_tracked_count_links(&nb); DEBUG_printf("m_tracked_free(%p, [%p, %p], nbytes=%u, links=%u;%u)\n", node, node->prev, node->next, (int)data_bytes, (int)n, (int)nb); diff --git a/py/manifest.cmake b/py/manifest.cmake new file mode 100644 index 00000000000..446fb063b7e --- /dev/null +++ b/py/manifest.cmake @@ -0,0 +1,73 @@ +# Extract c_module() entries from MICROPY_FROZEN_MANIFEST and append them to +# USER_C_MODULES. Included from py/usermod.cmake; the port must have resolved +# MICROPY_FROZEN_MANIFEST (and any board-config default / cmake-command-line +# override) before usermod.cmake runs. +# +# Also sets default MICROPY_MANIFEST_* path variables that are forwarded to +# makemanifest.py here for c_module extraction and again from py/mkrules.cmake +# for the frozen content generation step, so both passes substitute the same +# values into manifest path expressions. + +if(MICROPY_FROZEN_MANIFEST) + # Set default path variables to be passed to makemanifest.py. These are + # available in path substitutions inside the manifest. Additional variables + # can be set per-board in mpconfigboard.cmake or on the cmake command line. + # MICROPY_LIB_DIR is set in py.cmake (included before usermod.cmake). + if(NOT DEFINED MICROPY_MANIFEST_PORT_DIR) + set(MICROPY_MANIFEST_PORT_DIR ${MICROPY_PORT_DIR}) + endif() + if(NOT DEFINED MICROPY_MANIFEST_BOARD_DIR) + set(MICROPY_MANIFEST_BOARD_DIR ${MICROPY_BOARD_DIR}) + endif() + if(NOT DEFINED MICROPY_MANIFEST_MPY_DIR) + set(MICROPY_MANIFEST_MPY_DIR ${MICROPY_DIR}) + endif() + if(NOT DEFINED MICROPY_MANIFEST_MPY_LIB_DIR) + set(MICROPY_MANIFEST_MPY_LIB_DIR ${MICROPY_LIB_DIR}) + endif() + + # Find all MICROPY_MANIFEST_* variables and turn them into command line + # arguments. Stored in MICROPY_MAKEMANIFEST_ARGS (outside the + # MICROPY_MANIFEST_ namespace) so it doesn't self-match the regex above + # on subsequent cmake configures. + get_cmake_property(_manifest_vars VARIABLES) + list(FILTER _manifest_vars INCLUDE REGEX "MICROPY_MANIFEST_.*") + set(MICROPY_MAKEMANIFEST_ARGS) + foreach(_manifest_var IN LISTS _manifest_vars) + list(APPEND MICROPY_MAKEMANIFEST_ARGS "-v") + string(REGEX REPLACE "MICROPY_MANIFEST_(.*)" "\\1" _manifest_var_name ${_manifest_var}) + list(APPEND MICROPY_MAKEMANIFEST_ARGS "${_manifest_var_name}=${${_manifest_var}}") + endforeach() + + # Skip extraction during UPDATE_SUBMODULES (require() would fail) and when + # micropython-lib is not initialised. Matches py/manifest.mk behaviour: + # warn and continue with an empty c_module list rather than fatal. + if(EXISTS ${MICROPY_FROZEN_MANIFEST} AND NOT UPDATE_SUBMODULES) + if(EXISTS ${MICROPY_LIB_DIR}/README.md) + if(NOT Python3_EXECUTABLE) + find_package(Python3 REQUIRED COMPONENTS Interpreter) + endif() + + execute_process( + COMMAND "${Python3_EXECUTABLE}" "${MICROPY_DIR}/tools/makemanifest.py" + --list-c-modules ${MICROPY_MAKEMANIFEST_ARGS} "${MICROPY_FROZEN_MANIFEST}" + OUTPUT_VARIABLE MANIFEST_C_MODULES + ERROR_VARIABLE MANIFEST_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE MANIFEST_RESULT + ) + + if(NOT MANIFEST_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to extract C modules from manifest: ${MICROPY_FROZEN_MANIFEST}\nError: ${MANIFEST_ERROR}") + endif() + + if(MANIFEST_C_MODULES) + string(REPLACE "\n" ";" MANIFEST_C_MODULES_LIST "${MANIFEST_C_MODULES}") + list(APPEND USER_C_MODULES ${MANIFEST_C_MODULES_LIST}) + list(REMOVE_DUPLICATES USER_C_MODULES) + endif() + else() + message(WARNING "c_module() extraction skipped: micropython-lib not initialised, run 'make BOARD=${MICROPY_BOARD} submodules'") + endif() + endif() +endif() diff --git a/py/manifest.mk b/py/manifest.mk new file mode 100644 index 00000000000..43ad64be34e --- /dev/null +++ b/py/manifest.mk @@ -0,0 +1,58 @@ +# Manifest processing for freeze(), require(), include(), and c_module(). +# This file handles both C module extraction and frozen Python content generation. + +ifneq ($(FROZEN_MANIFEST),) + +# Set default path variables to be passed to makemanifest.py. These will be +# available in path substitutions. Additional variables can be set per-board +# in mpconfigboard.mk or on the make command line. +MICROPY_MANIFEST_PORT_DIR ?= $(CURDIR) +MICROPY_MANIFEST_BOARD_DIR ?= $(BOARD_DIR) +MICROPY_MANIFEST_MPY_DIR ?= $(TOP) +MICROPY_MANIFEST_MPY_LIB_DIR ?= $(MPY_LIB_DIR) + +# Find all MICROPY_MANIFEST_* variables and turn them into command line +# arguments. Values must not contain whitespace (GNU make word-splits). +MANIFEST_VARIABLES = $(foreach var,$(filter MICROPY_MANIFEST_%, $(.VARIABLES)),-v "$(subst MICROPY_MANIFEST_,,$(var))=$($(var))") + +# Skip c_module extraction for targets that don't need a build. +MANIFEST_NON_BUILD_GOALS := clean clean-prog submodules help print-cfg print-def +MANIFEST_GOALS := $(filter-out $(MANIFEST_NON_BUILD_GOALS),$(or $(MAKECMDGOALS),build)) + +# Extract C module paths up front so downstream rules see them. Skipped when +# micropython-lib isn't initialised (require() would fail) or for non-build +# targets. $(shell) collapses newlines, so c_module() paths cannot contain +# whitespace on the make side (the cmake side handles it correctly). +MANIFEST_C_MODULES := +ifneq ($(MANIFEST_GOALS),) +ifeq ($(wildcard $(FROZEN_MANIFEST)),$(FROZEN_MANIFEST)) +ifeq ($(wildcard $(MPY_LIB_DIR)/README.md),$(MPY_LIB_DIR)/README.md) +MANIFEST_C_MODULES := $(shell $(MAKE_MANIFEST) --list-c-modules $(MANIFEST_VARIABLES) $(FROZEN_MANIFEST)) +# Treat a populated, non-zero .SHELLSTATUS as failure. GNU make < 4.2 (eg +# macOS 3.81) leaves .SHELLSTATUS empty, in which case skip the check; an +# empty filter result means "succeed (status was 0, or unavailable)". +ifneq ($(filter-out 0,$(.SHELLSTATUS)),) +$(error makemanifest.py --list-c-modules failed (exit $(.SHELLSTATUS)) for $(FROZEN_MANIFEST)) +endif +else +$(warning c_module() extraction skipped: micropython-lib not initialised, run 'make submodules') +endif +endif +endif + +# Merge manifest c_modules into USER_C_MODULES. $(sort) also de-duplicates; +# this changes USER_C_MODULES iteration order vs cmake's list(REMOVE_DUPLICATES) +# which preserves first occurrence. Module include order should not be +# behaviour-sensitive so this is acceptable. +USER_C_MODULES := $(sort $(USER_C_MODULES) $(MANIFEST_C_MODULES)) + +# Frozen content rule, requires $(BUILD) and other variables from the port. +ifdef BUILD +# CIRCUITPY-CHANGE: FROZEN_MANIFEST is generated into $(BUILD) during the build +# (py/circuitpy_mpconfig.mk), so it must be a prerequisite here. +$(BUILD)/frozen_content.c: FORCE $(BUILD)/genhdr/qstrdefs.generated.h $(BUILD)/genhdr/root_pointers.h $(FROZEN_MANIFEST) | $(MICROPY_MPYCROSS_DEPENDENCY) + $(Q)test -e "$(MPY_LIB_DIR)/README.md" || (echo -e $(HELP_MPY_LIB_SUBMODULE); false) + $(Q)$(MAKE_MANIFEST) -o $@ $(MANIFEST_VARIABLES) -b "$(BUILD)" $(if $(MPY_CROSS_FLAGS),-f"$(MPY_CROSS_FLAGS)",) --mpy-tool-flags="$(MPY_TOOL_FLAGS)" $(FROZEN_MANIFEST) +endif + +endif # FROZEN_MANIFEST diff --git a/py/misc.h b/py/misc.h index d85a8f34289..a7498e66d17 100644 --- a/py/misc.h +++ b/py/misc.h @@ -165,6 +165,7 @@ MP_NORETURN void m_malloc_fail(size_t num_bytes); // These alloc/free functions track the pointers in a linked list so the GC does not reclaim // them. They can be used by code that requires traditional C malloc/free semantics. void *m_tracked_calloc(size_t nmemb, size_t size); +void *m_tracked_realloc(void *ptr_in, size_t n_bytes); void m_tracked_free(void *ptr_in); #endif @@ -291,8 +292,6 @@ void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap); int DEBUG_printf(const char *fmt, ...); -extern mp_uint_t mp_verbose_flag; - /** float internals *************/ #if MICROPY_PY_BUILTINS_FLOAT diff --git a/py/mkrules.cmake b/py/mkrules.cmake index e3d769cc59b..0903f81d897 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -260,9 +260,12 @@ if(MICROPY_FROZEN_MANIFEST) # Note: target_compile_definitions already added earlier. - if(NOT MICROPY_LIB_DIR) + # Ensure micropython-lib is included in submodule updates, but only when + # MICROPY_LIB_DIR points at the in-tree submodule location; a user + # overriding MICROPY_LIB_DIR to an out-of-tree checkout shouldn't have + # the submodule updated. + if(MICROPY_LIB_DIR STREQUAL "${MICROPY_DIR}/lib/micropython-lib") list(APPEND GIT_SUBMODULES lib/micropython-lib) - set(MICROPY_LIB_DIR ${MICROPY_DIR}/lib/micropython-lib) endif() if(NOT UPDATE_SUBMODULES AND NOT EXISTS ${MICROPY_LIB_DIR}/README.md) @@ -278,9 +281,13 @@ if(MICROPY_FROZEN_MANIFEST) if(NOT MICROPY_MAKE_EXECUTABLE) set(MICROPY_MAKE_EXECUTABLE make) endif() + # Clear FROZEN_MANIFEST/USER_C_MODULES in the mpy-cross sub-make so a + # shell-env FROZEN_MANIFEST=... doesn't leak in and trigger + # manifest.mk against the wrong cwd. mkrules.mk does the same for the + # make-based port path. add_custom_command( OUTPUT ${MICROPY_MPYCROSS_DEPENDENCY} - COMMAND ${MICROPY_MAKE_EXECUTABLE} -C ${MICROPY_DIR}/mpy-cross USER_C_MODULES= + COMMAND ${MICROPY_MAKE_EXECUTABLE} -C ${MICROPY_DIR}/mpy-cross USER_C_MODULES= FROZEN_MANIFEST= ) endif() @@ -290,27 +297,13 @@ if(MICROPY_FROZEN_MANIFEST) set(MICROPY_CROSS_FLAGS "-f${MICROPY_CROSS_FLAGS}") endif() - # Set default path variables to be passed to makemanifest.py. These will - # be available in path substitutions. Additional variables can be set - # per-board in mpconfigboard.cmake. - set(MICROPY_MANIFEST_PORT_DIR ${MICROPY_PORT_DIR}) - set(MICROPY_MANIFEST_BOARD_DIR ${MICROPY_BOARD_DIR}) - set(MICROPY_MANIFEST_MPY_DIR ${MICROPY_DIR}) - set(MICROPY_MANIFEST_MPY_LIB_DIR ${MICROPY_LIB_DIR}) - - # Find all MICROPY_MANIFEST_* variables and turn them into command line arguments. - get_cmake_property(_manifest_vars VARIABLES) - list(FILTER _manifest_vars INCLUDE REGEX "MICROPY_MANIFEST_.*") - foreach(_manifest_var IN LISTS _manifest_vars) - list(APPEND _manifest_var_args "-v") - string(REGEX REPLACE "MICROPY_MANIFEST_(.*)" "\\1" _manifest_var_name ${_manifest_var}) - list(APPEND _manifest_var_args "${_manifest_var_name}=${${_manifest_var}}") - endforeach() - + # MICROPY_MAKEMANIFEST_ARGS is populated by py/manifest.cmake (included via + # py/usermod.cmake before this file). Reuse that list so both the c_module + # extraction pass and the frozen content step substitute the same values. add_custom_target( BUILD_FROZEN_CONTENT ALL BYPRODUCTS ${MICROPY_FROZEN_CONTENT} - COMMAND ${Python3_EXECUTABLE} ${MICROPY_DIR}/tools/makemanifest.py -o ${MICROPY_FROZEN_CONTENT} ${_manifest_var_args} -b "${CMAKE_BINARY_DIR}" ${MICROPY_CROSS_FLAGS} --mpy-tool-flags=${MICROPY_MPY_TOOL_FLAGS} ${MICROPY_FROZEN_MANIFEST} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_DIR}/tools/makemanifest.py -o ${MICROPY_FROZEN_CONTENT} ${MICROPY_MAKEMANIFEST_ARGS} -b "${CMAKE_BINARY_DIR}" ${MICROPY_CROSS_FLAGS} --mpy-tool-flags=${MICROPY_MPY_TOOL_FLAGS} ${MICROPY_FROZEN_MANIFEST} DEPENDS ${MICROPY_QSTRDEFS_GENERATED} ${MICROPY_ROOT_POINTERS} diff --git a/py/mkrules.mk b/py/mkrules.mk index 03988996c71..fe4bf9a3d16 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -39,10 +39,24 @@ CFLAGS += -DMICROPY_BOARD_BUILD_NAME=\"$(BOARD)-$(BOARD_VARIANT)\" endif endif +# Add default C++ compiler flags based on CFLAGS. For use with C++ user modules. +# CIRCUITPY-CHANGE: CircuitPython enables C-only warnings that g++ rejects, in +# py/circuitpy_defns.mk and in the port Makefiles, so strip those too. +CXXFLAGS += $(filter-out -std=c11 -std=c99 -std=gnu11 -std=gnu99 -Werror-implicit-function-declaration -Werror=missing-prototypes -Werror=old-style-definition -Wmissing-prototypes -Wnested-externs -Wold-style-definition -Wstrict-prototypes,$(CFLAGS) $(CXXFLAGS_MOD)) + +# Add LDFLAGS to link libstdc++ on bare metal ports. Added only if a port has +# -nostdlib in LDFLAGS and C++ source files are provided. +ifneq ($(findstring nostdlib,"$(LDFLAGS)"),) +ifneq ($(SRC_CXX)$(SRC_USERMOD_CXX)$(SRC_USERMOD_LIB_CXX),) +LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" +LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" +endif +endif + # QSTR generation uses the same CFLAGS, with these modifications. QSTR_GEN_FLAGS = -DNO_QSTR # Note: := to force evaluation immediately. -QSTR_GEN_CFLAGS := $(CFLAGS) +QSTR_GEN_CFLAGS := $(filter-out -g%,$(CFLAGS)) QSTR_GEN_CFLAGS += $(QSTR_GEN_FLAGS) QSTR_GEN_CXXFLAGS := $(CXXFLAGS) QSTR_GEN_CXXFLAGS += $(QSTR_GEN_FLAGS) @@ -63,13 +77,13 @@ QSTR_GEN_CXXFLAGS += $(QSTR_GEN_FLAGS) # can be located. By following this scheme, it allows a single build rule # to be used to compile all .c files. +vpath %.S . $(TOP) $(USER_C_MODULES) $(USERMOD_DIR_PARENTS) # CIRCUITPY-CHANGE: use STEPECHO -vpath %.S . $(TOP) $(USER_C_MODULES) $(BUILD)/%.o: %.S $(STEPECHO) "CC $<" $(Q)$(CC) $(CFLAGS) -c -o $@ $< -vpath %.s . $(TOP) $(USER_C_MODULES) +vpath %.s . $(TOP) $(USER_C_MODULES) $(USERMOD_DIR_PARENTS) $(BUILD)/%.o: %.s $(STEPECHO) "AS $<" $(Q)$(AS) $(AFLAGS) -o $@ $< @@ -100,11 +114,11 @@ $(Q)$(CXX) $(CXXFLAGS) -c -MD -MF $(@:.o=.d) -o $@ $< || (echo -e $(HELP_BUILD_E endef # CIRCUITPY-CHANGE: add $(BUILD) -vpath %.c . $(TOP) $(USER_C_MODULES) $(BUILD) +vpath %.c . $(TOP) $(USER_C_MODULES) $(USERMOD_DIR_PARENTS) $(BUILD) $(BUILD)/%.o: %.c $(call compile_c) -vpath %.cpp . $(TOP) $(USER_C_MODULES) +vpath %.cpp . $(TOP) $(USER_C_MODULES) $(USERMOD_DIR_PARENTS) $(BUILD)/%.o: %.cpp $(call compile_cxx) @@ -196,9 +210,12 @@ $(HEADER_BUILD): $(Q)$(MKDIR) -p $@ ifneq ($(MICROPY_MPYCROSS_DEPENDENCY),) -# to automatically build mpy-cross, if needed +# Build mpy-cross automatically if needed. Clear USER_C_MODULES and +# FROZEN_MANIFEST so a port build with either set doesn't leak them into the +# mpy-cross sub-make and cause manifest.mk there to parse the port's manifest +# from the wrong cwd. $(MICROPY_MPYCROSS_DEPENDENCY): - $(MAKE) -C "$(abspath $(dir $@)..)" USER_C_MODULES= + $(MAKE) -C "$(abspath $(dir $@)..)" USER_C_MODULES= FROZEN_MANIFEST= endif ifneq ($(FROZEN_DIR),) @@ -220,12 +237,6 @@ endif CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool CFLAGS += -DMICROPY_MODULE_FROZEN_MPY CFLAGS += -DMICROPY_MODULE_FROZEN_STR - -# CIRCUITPY-CHANGE: FROZEN_MANIFEST is constructed at build time -# to build frozen_content.c from a manifest -$(BUILD)/frozen_content.c: FORCE $(BUILD)/genhdr/qstrdefs.generated.h $(BUILD)/genhdr/root_pointers.h $(FROZEN_MANIFEST) | $(MICROPY_MPYCROSS_DEPENDENCY) - $(Q)test -e "$(MPY_LIB_DIR)/README.md" || (echo -e $(HELP_MPY_LIB_SUBMODULE); false) - $(Q)$(MAKE_MANIFEST) -o $@ -v "MPY_DIR=$(TOP)" -v "MPY_LIB_DIR=$(MPY_LIB_DIR)" -v "PORT_DIR=$(shell pwd)" -v "BOARD_DIR=$(BOARD_DIR)" -b "$(BUILD)" $(if $(MPY_CROSS_FLAGS),-f"$(MPY_CROSS_FLAGS)",) --mpy-tool-flags="$(MPY_TOOL_FLAGS)" $(FROZEN_MANIFEST) endif ifneq ($(PROG),) @@ -244,7 +255,7 @@ $(BUILD)/$(PROG): $(OBJ) $(ECHO) "LINK $@" # Do not pass COPT here - it's *C* compiler optimizations. For example, # we may want to compile using Thumb, but link with non-Thumb libc. - $(Q)$(CC) -o $@ $^ $(LIB) $(LDFLAGS) + $(Q)$(CC) -o $@ $^ $(LIBS) $(LDFLAGS) ifndef DEBUG ifdef STRIP $(Q)$(STRIP) $(STRIPFLAGS_EXTRA) $@ diff --git a/py/modbuiltins.c b/py/modbuiltins.c index 186a79b7fac..327783996fb 100644 --- a/py/modbuiltins.c +++ b/py/modbuiltins.c @@ -138,7 +138,7 @@ static mp_obj_t mp_builtin_chr(mp_obj_t o_in) { #if MICROPY_PY_BUILTINS_STR_UNICODE mp_uint_t c = mp_obj_get_int(o_in); if (c >= 0x110000) { - mp_raise_ValueError(MP_ERROR_TEXT("chr() arg not in range(0x110000)")); + mp_raise_ValueError(MP_ERROR_TEXT("char not in range(0x110000)")); } VSTR_FIXED(buf, 4); vstr_add_char(&buf, c); @@ -155,6 +155,7 @@ static mp_obj_t mp_builtin_chr(mp_obj_t o_in) { } MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr); +#if MICROPY_PY_BUILTINS_DIR static mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { mp_obj_t dir = mp_obj_new_list(0, NULL); if (n_args == 0) { @@ -188,6 +189,7 @@ static mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) { return dir; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_dir_obj, 0, 1, mp_builtin_dir); +#endif static mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) { return mp_binary_op(MP_BINARY_OP_DIVMOD, o1_in, o2_in); @@ -366,18 +368,21 @@ static mp_obj_t mp_builtin_ord(mp_obj_t o_in) { MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_ord_obj, mp_builtin_ord); static mp_obj_t mp_builtin_pow(size_t n_args, const mp_obj_t *args) { - switch (n_args) { - case 2: - return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); - default: - #if !MICROPY_PY_BUILTINS_POW3 - mp_raise_NotImplementedError(MP_ERROR_TEXT("3-arg pow() not supported")); - #elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ - return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); - #else - return mp_obj_int_pow3(args[0], args[1], args[2]); - #endif + // Treat pow(x, y, None) as pow(x, y), matching CPython. + if (n_args == 2 + #if MICROPY_PY_BUILTINS_POW3 + || args[2] == mp_const_none + #endif + ) { + return mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]); } + #if !MICROPY_PY_BUILTINS_POW3 + mp_raise_NotImplementedError(MP_ERROR_TEXT("3-arg pow() not supported")); + #elif MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_MPZ + return mp_binary_op(MP_BINARY_OP_MODULO, mp_binary_op(MP_BINARY_OP_POWER, args[0], args[1]), args[2]); + #else + return mp_obj_int_pow3(args[0], args[1], args[2]); + #endif } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_pow_obj, 2, 3, mp_builtin_pow); @@ -692,7 +697,9 @@ static const mp_rom_map_elem_t mp_module_builtins_globals_table[] = { #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR_delattr), MP_ROM_PTR(&mp_builtin_delattr_obj) }, #endif + #if MICROPY_PY_BUILTINS_DIR { MP_ROM_QSTR(MP_QSTR_dir), MP_ROM_PTR(&mp_builtin_dir_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_divmod), MP_ROM_PTR(&mp_builtin_divmod_obj) }, #if MICROPY_PY_BUILTINS_EVAL_EXEC { MP_ROM_QSTR(MP_QSTR_eval), MP_ROM_PTR(&mp_builtin_eval_obj) }, diff --git a/py/moderrno.c b/py/moderrno.c index 140ae1cc0a6..e70b89bce5a 100644 --- a/py/moderrno.c +++ b/py/moderrno.c @@ -44,6 +44,7 @@ X(EAGAIN) \ X(ENOMEM) \ X(EACCES) \ + X(EBUSY) \ X(EEXIST) \ X(ENODEV) \ X(EISDIR) \ diff --git a/py/modio.c b/py/modio.c index 9aeb42d30aa..fdee484376f 100644 --- a/py/modio.c +++ b/py/modio.c @@ -63,6 +63,10 @@ static mp_uint_t iobase_read_write(mp_obj_t obj, void *buf, mp_uint_t size, int } mp_int_t ret = mp_obj_get_int(ret_obj); if (ret >= 0) { + if ((mp_uint_t)ret > size) { + *errcode = MP_EIO; + return MP_STREAM_ERROR; + } return ret; } else { *errcode = -ret; diff --git a/py/modmicropython.c b/py/modmicropython.c index 67bb32626c3..5e1b3253427 100644 --- a/py/modmicropython.c +++ b/py/modmicropython.c @@ -163,13 +163,35 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mp_micropython_kbd_intr_obj, mp_micropython_kbd #endif #if MICROPY_ENABLE_SCHEDULER + +#if MICROPY_KBD_EXCEPTION && MICROPY_SCHEDULER_STATIC_NODES +static mp_sched_node_t mp_keyboard_interrupt_sched_node; +static void mp_sched_keyboard_interrupt_wrapper(mp_sched_node_t *node) { + mp_sched_keyboard_interrupt(); +} +#endif + static mp_obj_t mp_micropython_schedule(mp_obj_t function, mp_obj_t arg) { + #if MICROPY_KBD_EXCEPTION + if (function == MP_OBJ_FROM_PTR(&mp_micropython_kbd_intr_obj)) { + #if MICROPY_SCHEDULER_STATIC_NODES + // Allow calling `micropython.schedule(micropython.kbd_intr, None)` as a + // special case to schedule a keyboard interrupt. + mp_sched_schedule_node(&mp_keyboard_interrupt_sched_node, mp_sched_keyboard_interrupt_wrapper); + return mp_const_none; + #else + // Signal that passing `micropython.kbd_intr` is not supported. + mp_raise_ValueError(NULL); + #endif + } + #endif if (!mp_sched_schedule(function, arg)) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("schedule queue full")); } return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_2(mp_micropython_schedule_obj, mp_micropython_schedule); + #endif static const mp_rom_map_elem_t mp_module_micropython_globals_table[] = { diff --git a/py/mpconfig.h b/py/mpconfig.h index 846209e9412..39a8681d73a 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -49,7 +49,7 @@ // as well as a fallback to generate MICROPY_GIT_TAG if the git repo or tags // are unavailable. #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 28 +#define MICROPY_VERSION_MINOR 29 #define MICROPY_VERSION_MICRO 0 #define MICROPY_VERSION_PRERELEASE 0 @@ -479,7 +479,11 @@ typedef uint64_t mp_uint_t; // Whether to emit ARMv7-M instruction support in thumb native code #ifndef MICROPY_EMIT_THUMB_ARMV7M +#if defined(__ARM_ARCH_ISA_THUMB) && __ARM_ARCH_ISA_THUMB == 2 #define MICROPY_EMIT_THUMB_ARMV7M (1) +#else +#define MICROPY_EMIT_THUMB_ARMV7M (0) +#endif #endif // Whether to enable the thumb inline assembler @@ -489,7 +493,11 @@ typedef uint64_t mp_uint_t; // Whether to enable float support in the Thumb2 inline assembler #ifndef MICROPY_EMIT_INLINE_THUMB_FLOAT +#if defined(__ARM_ARCH_ISA_THUMB) && __ARM_ARCH_ISA_THUMB == 2 && defined(__ARM_FP) #define MICROPY_EMIT_INLINE_THUMB_FLOAT (1) +#else +#define MICROPY_EMIT_INLINE_THUMB_FLOAT (0) +#endif #endif // Whether to emit ARM native code @@ -548,7 +556,12 @@ typedef uint64_t mp_uint_t; // Some architectures cannot read byte-wise from executable memory. In this case // the prelude for a native function (which usually sits after the machine code) // must be separated and placed somewhere where it can be read byte-wise. +// CIRCUITPY-CHANGE: also when only loading native code on windowed Xtensa. +#if defined(__XTENSA_WINDOWED_ABI__) +#define MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE (MICROPY_EMIT_XTENSAWIN || MICROPY_PERSISTENT_CODE_LOAD_NATIVE) +#else #define MICROPY_EMIT_NATIVE_PRELUDE_SEPARATE_FROM_MACHINE_CODE (MICROPY_EMIT_XTENSAWIN) +#endif // Convenience definition for whether any inline assembler emitter is enabled #define MICROPY_EMIT_INLINE_ASM (MICROPY_EMIT_INLINE_THUMB || MICROPY_EMIT_INLINE_XTENSA || MICROPY_EMIT_INLINE_RV32) @@ -1273,6 +1286,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_VFS (0) #endif +// Whether to include support for fast native block devices. +#ifndef MICROPY_VFS_BLOCKDEV_NATIVE +#define MICROPY_VFS_BLOCKDEV_NATIVE (0) +#endif + // Whether to include support for writable filesystems. #ifndef MICROPY_VFS_WRITABLE #define MICROPY_VFS_WRITABLE (1) @@ -1397,7 +1415,7 @@ typedef time_t mp_timestamp_t; // Whether str object is proper unicode #ifndef MICROPY_PY_BUILTINS_STR_UNICODE -#define MICROPY_PY_BUILTINS_STR_UNICODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#define MICROPY_PY_BUILTINS_STR_UNICODE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES) #endif // Whether to check for valid UTF-8 when converting bytes to str @@ -1405,6 +1423,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_BUILTINS_STR_UNICODE_CHECK (MICROPY_PY_BUILTINS_STR_UNICODE) #endif +// Whether bytes.decode() supports the 'ignore' and 'replace' error handlers +#ifndef MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS +#define MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + // Whether str.center() method provided #ifndef MICROPY_PY_BUILTINS_STR_CENTER #define MICROPY_PY_BUILTINS_STR_CENTER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) @@ -1456,7 +1479,7 @@ typedef time_t mp_timestamp_t; // Whether to support memoryview.itemsize attribute #ifndef MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE -#define MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#define MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE (MICROPY_PY_MACHINE_MEM_BACKUP || MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES) #endif // Whether to support set object @@ -1514,6 +1537,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_BUILTINS_ROUND_INT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Whether to implement dir() to enumerate object fields. +#ifndef MICROPY_PY_BUILTINS_DIR +#define MICROPY_PY_BUILTINS_DIR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) +#endif + // Whether to support complete set of special methods for user // classes, or only the most used ones. "Inplace" methods are // controlled by MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS below. @@ -1604,6 +1632,16 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_BUILTINS_HELP_MODULES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Use this to configure output of help('modules') +#ifndef MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS +#define MICROPY_PY_BUILTINS_HELP_NUM_COLUMNS (4) +#endif + +// Use this to configure output of help('modules') +#ifndef MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH +#define MICROPY_PY_BUILTINS_HELP_COLUMN_WIDTH (18) +#endif + // Whether to provide mem-info related functions in micropython module #ifndef MICROPY_PY_MICROPYTHON_MEM_INFO #define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) @@ -1907,7 +1945,7 @@ typedef time_t mp_timestamp_t; // implementation). This is present for compatibility but can be disabled to // save space. #ifndef MICROPY_PY_SELECT_SELECT -#define MICROPY_PY_SELECT_SELECT (1) +#define MICROPY_PY_SELECT_SELECT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif // Whether to provide the "time" module @@ -2116,6 +2154,11 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_MACHINE_MEMX (MICROPY_PY_MACHINE) #endif +// Whether to provide the "machine.mem_backup" function +#ifndef MICROPY_PY_MACHINE_MEM_BACKUP +#define MICROPY_PY_MACHINE_MEM_BACKUP (0) +#endif + // Whether to provide the "machine.Signal" class #ifndef MICROPY_PY_MACHINE_SIGNAL #define MICROPY_PY_MACHINE_SIGNAL (MICROPY_PY_MACHINE) @@ -2160,6 +2203,16 @@ typedef time_t mp_timestamp_t; #define MICROPY_PY_SOCKET_LISTEN_BACKLOG_DEFAULT (2) #endif +// Whether to enable lwIP bindings to be used as the implementation of the `socket` module +#ifndef MICROPY_PY_LWIP +#define MICROPY_PY_LWIP (0) +#endif + +// Whether to support raw sockets via the `socket.SOCK_RAW` constant +#ifndef MICROPY_PY_LWIP_SOCK_RAW +#define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) +#endif + #ifndef MICROPY_PY_SSL #define MICROPY_PY_SSL (0) #endif diff --git a/py/mpstate.h b/py/mpstate.h index 8519a1a32ad..25685334a4c 100644 --- a/py/mpstate.h +++ b/py/mpstate.h @@ -80,6 +80,9 @@ typedef struct mp_dynamic_compiler_t { uint8_t small_int_bits; // must be <= host small_int_bits uint8_t native_arch; uint8_t nlr_buf_num_regs; + #if MICROPY_ENABLE_SOURCE_LINE + bool include_source_lines; + #endif } mp_dynamic_compiler_t; extern mp_dynamic_compiler_t mp_dynamic_compiler; #endif @@ -141,6 +144,10 @@ typedef struct _mp_state_mem_t { #endif mp_state_mem_area_t area; + #if MICROPY_GC_SPLIT_HEAP + byte *area_pool_min; // Min of all gc_pool_start values across all areas + byte *area_pool_max; // Max of all gc_pool_end values across all areas + #endif int gc_stack_overflow; MICROPY_GC_STACK_ENTRY_TYPE gc_block_stack[MICROPY_ALLOC_GC_STACK_SIZE]; @@ -254,6 +261,9 @@ typedef struct _mp_state_vm_t { #if MICROPY_EMIT_NATIVE uint8_t default_emit_opt; // one of MP_EMIT_OPT_xxx #endif + #if MICROPY_DEBUG_PRINTERS + mp_uint_t mp_verbose_flag; + #endif #endif // size of the emergency exception buf, if it's dynamically allocated diff --git a/py/mpz.c b/py/mpz.c index 74fd1754636..062777af566 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -1360,7 +1360,7 @@ void mpz_pow_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs) { can have dest, lhs, rhs the same; mod can't be the same as dest */ void mpz_pow3_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs, const mpz_t *mod) { - if (lhs->len == 0 || rhs->neg != 0 || (mod->len == 1 && mod->dig[0] == 1)) { + if (rhs->neg != 0 || (mod->len == 1 && mod->dig[0] == 1)) { mpz_set_from_int(dest, 0); return; } @@ -1368,6 +1368,15 @@ void mpz_pow3_inpl(mpz_t *dest, const mpz_t *lhs, const mpz_t *rhs, const mpz_t mpz_set_from_int(dest, 1); if (rhs->len == 0) { + // Python style modulo: 1 % mod is 1 + mod, as abs(mod) >= 2 here + if (mod->neg) { + mpz_add_inpl(dest, dest, mod); + } + return; + } + + if (lhs->len == 0) { + mpz_set_from_int(dest, 0); return; } @@ -1602,16 +1611,17 @@ bool mpz_as_bytes(const mpz_t *z, bool big_endian, bool as_signed, size_t len, b b += len; } mpz_dig_t *zdig = z->dig; + byte fill_byte = z->neg ? 0xFF : 0x00; int bits = 0; mpz_dbl_dig_t d = 0; mpz_dbl_dig_t carry = 1; + mpz_dig_t val = 0; size_t olen = len; // bytes in output buffer - bool ok = true; for (size_t zlen = z->len; zlen > 0; --zlen) { bits += DIG_SIZE; d = (d << DIG_SIZE) | *zdig++; for (; bits >= 8; bits -= 8, d >>= 8) { - mpz_dig_t val = d; + val = d; if (z->neg) { val = (~val & 0xff) + carry; carry = val >> 8; @@ -1619,7 +1629,9 @@ bool mpz_as_bytes(const mpz_t *z, bool big_endian, bool as_signed, size_t len, b if (!olen) { // Buffer is full, only OK if all remaining bytes are zeroes - ok = ok && ((byte)val == 0); + if ((byte)val != fill_byte) { + return false; + } continue; } @@ -1632,16 +1644,17 @@ bool mpz_as_bytes(const mpz_t *z, bool big_endian, bool as_signed, size_t len, b } } - if (as_signed && olen == 0 && len > 0) { - // If output exhausted then ensure there was enough space for the sign bit - byte most_sig = big_endian ? buf[0] : buf[len - 1]; - ok = ok && (bool)(most_sig & 0x80) == (bool)z->neg; - } else { + // Check if the most significant bit is set incorrectly for a signed value + if (olen == 0 && as_signed && ((val & 0x80) != (fill_byte & 0x80))) { + return false; + } + + if (olen > 0) { // fill remainder of buf with zero/sign extension of the integer - memset(big_endian ? buf : b, z->neg ? 0xff : 0x00, olen); + memset(big_endian ? buf : b, fill_byte, olen); } - return ok; + return true; } #if MICROPY_PY_BUILTINS_FLOAT diff --git a/py/nativeglue.c b/py/nativeglue.c index 2613312e2b7..c632cd36534 100644 --- a/py/nativeglue.c +++ b/py/nativeglue.c @@ -323,7 +323,11 @@ const mp_fun_table_t mp_fun_table = { mp_printf, mp_vprintf, // CIRCUITPY-CHANGE: mp_raise_msg_str instead of mp_raise_msg + #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE + NULL, + #else mp_raise_msg_str, + #endif mp_obj_get_type, mp_obj_new_str, mp_obj_new_bytes, diff --git a/py/nlr.h b/py/nlr.h index 446e78c7ebb..72ea1368e93 100644 --- a/py/nlr.h +++ b/py/nlr.h @@ -46,6 +46,7 @@ #define MICROPY_NLR_NUM_REGS_XTENSAWIN (17) #define MICROPY_NLR_NUM_REGS_RV32I (14) #define MICROPY_NLR_NUM_REGS_RV64I (14) +#define MICROPY_NLR_NUM_REGS_LOONG64 (13) // *FORMAT-OFF* @@ -111,6 +112,13 @@ #else #error Unsupported RISC-V variant. #endif +#elif defined(__loongarch__) + #if defined(__loongarch64) + #define MICROPY_NLR_LOONG64 (1) + #define MICROPY_NLR_NUM_REGS (MICROPY_NLR_NUM_REGS_LOONG64) + #else + #error Unsupported Loongarch variant. + #endif #else #define MICROPY_NLR_SETJMP (1) //#warning "No native NLR support for this arch, using setjmp implementation" @@ -122,6 +130,10 @@ #define MICROPY_NLR_AARCH64 (0) #endif +#ifndef MICROPY_NLR_LOONG64 +#define MICROPY_NLR_LOONG64 (0) +#endif + #ifndef MICROPY_NLR_MIPS #define MICROPY_NLR_MIPS (0) #endif diff --git a/py/nlrloong64.c b/py/nlrloong64.c new file mode 100644 index 00000000000..7a12b2ec904 --- /dev/null +++ b/py/nlrloong64.c @@ -0,0 +1,80 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 Alessandro Gatti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mpstate.h" + +#if MICROPY_NLR_LOONG64 + +__attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr); + +__asm( + ".globl nlr_push \n" + "nlr_push: \n" + ".cfi_startproc \n" + "st.d $r1, $r4, 16 \n" /* Store RA. */ + "st.d $r23, $r4, 24 \n" /* Store S0. */ + "st.d $r24, $r4, 32 \n" /* Store S1. */ + "st.d $r25, $r4, 40 \n" /* Store S2. */ + "st.d $r26, $r4, 48 \n" /* Store S3. */ + "st.d $r27, $r4, 56 \n" /* Store S4. */ + "st.d $r28, $r4, 64 \n" /* Store S5. */ + "st.d $r29, $r4, 72 \n" /* Store S6. */ + "st.d $r30, $r4, 80 \n" /* Store S7. */ + "st.d $r31, $r4, 88 \n" /* Store S8. */ + "st.d $r22, $r4, 96 \n" /* Store S9. */ + "st.d $r21, $r4, 104 \n" /* Marked as reserved in the ABI. */ + "st.d $r3, $r4, 112 \n" /* Store SP. */ + "b nlr_push_tail \n" /* Jump to the C part. */ + ".cfi_endproc \n" + ); + +MP_NORETURN void nlr_jump(void *val) { + MP_NLR_JUMP_HEAD(val, top) + __asm volatile ( + "add.d $r4, $r0, %0 \n" + "ld.d $r1, $r4, 16 \n" /* Retrieve RA. */ + "ld.d $r23, $r4, 24 \n" /* Retrieve S0. */ + "ld.d $r24, $r4, 32 \n" /* Retrieve S1. */ + "ld.d $r25, $r4, 40 \n" /* Retrieve S2. */ + "ld.d $r26, $r4, 48 \n" /* Retrieve S3. */ + "ld.d $r27, $r4, 56 \n" /* Retrieve S4. */ + "ld.d $r28, $r4, 64 \n" /* Retrieve S5. */ + "ld.d $r29, $r4, 72 \n" /* Retrieve S6. */ + "ld.d $r30, $r4, 80 \n" /* Retrieve S7. */ + "ld.d $r31, $r4, 88 \n" /* Retrieve S8. */ + "ld.d $r22, $r4, 96 \n" /* Retrieve S9. */ + "ld.d $r21, $r4, 104 \n" + "ld.d $r3, $r4, 112 \n" /* Retrieve SP. */ + "addi.d $r4, $r0, 1 \n" /* Return 1 for a non-local return. */ + "ret \n" /* Return. */ + : + : "r" (top) + : "memory" + ); + MP_UNREACHABLE +} + +#endif diff --git a/py/nlrpowerpc.c b/py/nlrpowerpc.c index cf140400e68..cb94473968d 100644 --- a/py/nlrpowerpc.c +++ b/py/nlrpowerpc.c @@ -72,7 +72,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { "bctr ;" : : "r" (&nlr->regs), "r" (nlr) - : + : "r4" ); return 0; @@ -82,33 +82,34 @@ MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm__ volatile ( - "ld 3, 0x0(%0) ;" + "mr 4, %0 ;" + "ld 3, 0x0(4) ;" "cmpdi 3, 0x4eed ; " // Check canary "bne . ; " - "ld 0, 0x08(%0) ;" - "ld 1, 0x10(%0) ;" - "ld 2, 0x18(%0) ;" - "ld 14, 0x20(%0) ;" - "ld 15, 0x28(%0) ;" - "ld 16, 0x30(%0) ;" - "ld 17, 0x38(%0) ;" - "ld 18, 0x40(%0) ;" - "ld 19, 0x48(%0) ;" - "ld 20, 0x50(%0) ;" - "ld 21, 0x58(%0) ;" - "ld 22, 0x60(%0) ;" - "ld 23, 0x68(%0) ;" - "ld 24, 0x70(%0) ;" - "ld 25, 0x78(%0) ;" - "ld 26, 0x80(%0) ;" - "ld 27, 0x88(%0) ;" - "ld 28, 0x90(%0) ;" - "ld 29, 0x98(%0) ;" - "ld 30, 0xA0(%0) ;" - "ld 31, 0xA8(%0) ;" - "ld 3, 0xB0(%0) ;" + "ld 0, 0x08(4) ;" + "ld 1, 0x10(4) ;" + "ld 2, 0x18(4) ;" + "ld 14, 0x20(4) ;" + "ld 15, 0x28(4) ;" + "ld 16, 0x30(4) ;" + "ld 17, 0x38(4) ;" + "ld 18, 0x40(4) ;" + "ld 19, 0x48(4) ;" + "ld 20, 0x50(4) ;" + "ld 21, 0x58(4) ;" + "ld 22, 0x60(4) ;" + "ld 23, 0x68(4) ;" + "ld 24, 0x70(4) ;" + "ld 25, 0x78(4) ;" + "ld 26, 0x80(4) ;" + "ld 27, 0x88(4) ;" + "ld 28, 0x90(4) ;" + "ld 29, 0x98(4) ;" + "ld 30, 0xA0(4) ;" + "ld 31, 0xA8(4) ;" + "ld 3, 0xB0(4) ;" "mtcr 3 ;" - "ld 3, 0xB8(%0) ;" + "ld 3, 0xB8(4) ;" "mtlr 3 ; " "li 3, 1;" "blr ;" @@ -161,7 +162,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { "bctr ;" : : "r" (&nlr->regs), "r" (nlr) - : + : "r4" ); return 0; @@ -171,33 +172,34 @@ MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm__ volatile ( - "l 3, 0x0(%0) ;" + "mr 4, %0 ;" + "l 3, 0x0(4) ;" "cmpdi 3, 0x4eed ; " // Check canary "bne . ; " - "l 0, 0x04(%0) ;" - "l 1, 0x08(%0) ;" - "l 2, 0x0c(%0) ;" - "l 14, 0x10(%0) ;" - "l 15, 0x14(%0) ;" - "l 16, 0x18(%0) ;" - "l 17, 0x1c(%0) ;" - "l 18, 0x20(%0) ;" - "l 19, 0x24(%0) ;" - "l 20, 0x28(%0) ;" - "l 21, 0x2c(%0) ;" - "l 22, 0x30(%0) ;" - "l 23, 0x34(%0) ;" - "l 24, 0x38(%0) ;" - "l 25, 0x3c(%0) ;" - "l 26, 0x40(%0) ;" - "l 27, 0x44(%0) ;" - "l 28, 0x48(%0) ;" - "l 29, 0x4c(%0) ;" - "l 30, 0x50(%0) ;" - "l 31, 0x54(%0) ;" - "l 3, 0x58(%0) ;" + "l 0, 0x04(4) ;" + "l 1, 0x08(4) ;" + "l 2, 0x0c(4) ;" + "l 14, 0x10(4) ;" + "l 15, 0x14(4) ;" + "l 16, 0x18(4) ;" + "l 17, 0x1c(4) ;" + "l 18, 0x20(4) ;" + "l 19, 0x24(4) ;" + "l 20, 0x28(4) ;" + "l 21, 0x2c(4) ;" + "l 22, 0x30(4) ;" + "l 23, 0x34(4) ;" + "l 24, 0x38(4) ;" + "l 25, 0x3c(4) ;" + "l 26, 0x40(4) ;" + "l 27, 0x44(4) ;" + "l 28, 0x48(4) ;" + "l 29, 0x4c(4) ;" + "l 30, 0x50(4) ;" + "l 31, 0x54(4) ;" + "l 3, 0x58(4) ;" "mtcr 3 ;" - "l 3, 0x5c(%0) ;" + "l 3, 0x5c(4) ;" "mtlr 3 ; " "li 3, 1;" "blr ;" diff --git a/py/nlrx86.c b/py/nlrx86.c index 26bf0dc6ccb..82e139dd8e8 100644 --- a/py/nlrx86.c +++ b/py/nlrx86.c @@ -40,23 +40,35 @@ __attribute__((used)) unsigned int nlr_push_tail(nlr_buf_t *nlr); #endif #if !defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 8 -// Since gcc 8.0 the naked attribute is supported -#define USE_NAKED (1) -#define UNDO_PRELUDE (0) +// Since gcc 8.0 the naked and no-sanitize attributes are supported + #define NLR_PUSH_ATTRIBUTE __attribute__((naked, no_sanitize("unreachable"))) + #define UNDO_PRELUDE (0) + #define ARG_USED(x) (void)x; + #define NLR_UNREACHABLE __builtin_unreachable(); #elif defined(__ZEPHYR__) || defined(__ANDROID__) // Zephyr and Android use a different calling convention by default -#define USE_NAKED (0) -#define UNDO_PRELUDE (0) + #define NLR_PUSH_ATTRIBUTE /* NOTHING */ + #define UNDO_PRELUDE (0) + #define ARG_USED(x) (void)x; + #define NLR_UNREACHABLE return 0; +#elif defined(__clang__) +// clang on Ubuntu 24.04 enables -fsanitize=unreachable by default, but this +// destroys the content of the ebx register. + #define NLR_PUSH_ATTRIBUTE __attribute__((naked, no_sanitize("unreachable"))) + #define UNDO_PRELUDE (0) + #define ARG_USED(x) /* NOTHING */ + #define NLR_UNREACHABLE /* NOTHING */ #else -#define USE_NAKED (0) -#define UNDO_PRELUDE (1) +// gcc before 8 unavoidably emits a 'push %ebp' prologue instruction + #define NLR_PUSH_ATTRIBUTE /* NOTHING */ + #define UNDO_PRELUDE (1) + #define ARG_USED(x) (void)x; + #define NLR_UNREACHABLE return 0; #endif -#if USE_NAKED -__attribute__((naked)) -#endif +NLR_PUSH_ATTRIBUTE unsigned int nlr_push(nlr_buf_t *nlr) { - (void)nlr; + ARG_USED(nlr) __asm volatile ( #if UNDO_PRELUDE @@ -73,9 +85,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { "jmp nlr_push_tail \n" // do the rest in C ); - #if !USE_NAKED - return 0; // needed to silence compiler warning - #endif + NLR_UNREACHABLE } MP_NORETURN void nlr_jump(void *val) { diff --git a/py/obj.c b/py/obj.c index ba3e6a9a996..b65ac53d23a 100644 --- a/py/obj.c +++ b/py/obj.c @@ -441,7 +441,7 @@ long long mp_obj_get_ll(mp_const_obj_t arg) { return MP_OBJ_SMALL_INT_VALUE(arg); } else { long long res; - mp_obj_int_to_bytes_impl((mp_obj_t)arg, MP_ENDIANNESS_BIG, sizeof(res), (byte *)&res); + mp_obj_int_to_bytes((mp_obj_t)arg, sizeof(res), (byte *)&res, MP_ENDIANNESS_BIG, false, false); return res; } } diff --git a/py/obj.h b/py/obj.h index e12cbff1ca2..368ac96dbd2 100644 --- a/py/obj.h +++ b/py/obj.h @@ -117,7 +117,7 @@ extern const struct _mp_obj_float_t mp_const_float_inf_obj; extern const struct _mp_obj_float_t mp_const_float_nan_obj; #endif -#define mp_obj_is_float(o) mp_obj_is_type((o), &mp_type_float) +#define mp_obj_is_float(o) mp_obj_is_exact_type((o), &mp_type_float) mp_float_t mp_obj_float_get(mp_obj_t self_in); mp_obj_t mp_obj_new_float(mp_float_t value); #endif @@ -162,7 +162,7 @@ extern const struct _mp_obj_float_t mp_const_float_inf_obj; extern const struct _mp_obj_float_t mp_const_float_nan_obj; #endif -#define mp_obj_is_float(o) mp_obj_is_type((o), &mp_type_float) +#define mp_obj_is_float(o) mp_obj_is_exact_type((o), &mp_type_float) mp_float_t mp_obj_float_get(mp_obj_t self_in); mp_obj_t mp_obj_new_float(mp_float_t value); #endif @@ -811,25 +811,6 @@ typedef struct _mp_obj_full_type_t { #define _MP_OBJ_TYPE_SLOT_TYPE_parent (const void *) #define _MP_OBJ_TYPE_SLOT_TYPE_locals_dict (struct _mp_obj_dict_t *) -// Implementation of MP_DEFINE_CONST_OBJ_TYPE for each number of arguments. -// Do not use these directly, instead use MP_DEFINE_CONST_OBJ_TYPE. -// Generated with: -// for i in range(13): -// print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}(_struct_type, _typename, _name, _flags{''.join(f', f{j+1}, v{j+1}' for j in range(i))}) const _struct_type _typename = {{ .base = {{ &mp_type_type }}, .flags = _flags, .name = _name{''.join(f', .slot_index_##f{j+1} = {j+1}' for j in range(i))}{', .slots = { ' + ''.join(f'v{j+1}, ' for j in range(i)) + '}' if i else '' } }}") -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { v1, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { v1, v2, v3, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { v1, v2, v3, v4, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { v1, v2, v3, v4, v5, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { v1, v2, v3, v4, v5, v6, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { v1, v2, v3, v4, v5, v6, v7, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, } } - // Because the mp_obj_type_t instances are in (zero-initialised) ROM, we take // slot_index_foo=0 to mean that the slot is unset. This also simplifies checking // if the slot is set. That means that we need to store index+1 in slot_index_foo @@ -843,6 +824,34 @@ typedef struct _mp_obj_full_type_t { #define MP_OBJ_TYPE_OFFSETOF_SLOT(f) (offsetof(mp_obj_type_t, slot_index_##f)) #define MP_OBJ_TYPE_HAS_SLOT_BY_OFFSET(t, offset) (*(uint8_t *)((char *)(t) + (offset)) != 0) +// Implementation of MP_DEFINE_CONST_OBJ_TYPE for each number of arguments. +// Do not use these directly, instead use MP_DEFINE_CONST_OBJ_TYPE. +// Generated with: +// for i in range(13): +// args = ['_struct_type', '_typename', '_name', '_flags'] +// struct = ['.base = { &mp_type_type }', '.flags = _flags', '.name = _name'] +// slots = [] +// for j in range(i): +// args += [f"f{j+1}", f"v{j+1}"] +// struct += [f".slot_index_##f{j+1} = {j+1}"] +// slots += [f"(const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f{j+1} v{j+1}"] +// if slots: +// struct += [f".slots = {{ {', '.join(slots)} }}"] +// print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}({', '.join(args)}) const _struct_type _typename = {{ {', '.join(struct)} }}") +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f9 v9 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f10 v10 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f10 v10, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f11 v11 } } +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f1 v1, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f2 v2, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f3 v3, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f4 v4, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f5 v5, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f6 v6, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f7 v7, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f8 v8, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f9 v9, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f10 v10, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f11 v11, (const void *)_MP_OBJ_TYPE_SLOT_TYPE_##f12 v12 } } + // Workaround for https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview?view=msvc-160#macro-arguments-are-unpacked #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x @@ -1042,8 +1051,13 @@ void *mp_obj_malloc_with_finaliser_helper(size_t num_bytes, const mp_obj_type_t MP_STATIC_ASSERT_NONCONSTEXPR((t) != &mp_type_str), assert((t) != &mp_type_str), \ MP_STATIC_ASSERT_NONCONSTEXPR((t) != &mp_type_NoneType), assert((t) != &mp_type_NoneType), \ 1) +#if MICROPY_PY_BUILTINS_FLOAT +#define mp_type_assert_not_float(t) (MP_STATIC_ASSERT_NONCONSTEXPR((t) != &mp_type_float), assert((t) != &mp_type_float), 1) +#else +#define mp_type_assert_not_float(t) (1) +#endif -#define mp_obj_is_type(o, t) (mp_type_assert_not_bool_int_str_nonetype(t) && mp_obj_is_exact_type(o, t)) +#define mp_obj_is_type(o, t) (mp_type_assert_not_bool_int_str_nonetype(t) && mp_type_assert_not_float(t) && mp_obj_is_exact_type(o, t)) #if MICROPY_OBJ_IMMEDIATE_OBJS // bool's are immediates, not real objects, so test for the 2 possible values. #define mp_obj_is_bool(o) ((o) == mp_const_false || (o) == mp_const_true) diff --git a/py/objexcept.c b/py/objexcept.c index 6a2fecd51e5..6efb0f50672 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -166,7 +166,7 @@ void mp_obj_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kin mp_print_kind_t k = kind & ~PRINT_EXC_SUBCLASS; bool is_subclass = kind & PRINT_EXC_SUBCLASS; if (!is_subclass && (k == PRINT_REPR || k == PRINT_EXC)) { - mp_print_str(print, qstr_str(o->base.type->name)); + mp_print_str(print, mp_obj_get_type_str(o_in)); } if (k == PRINT_EXC) { diff --git a/py/objint.c b/py/objint.c index cabbd7b8f0e..e8f7f51052a 100644 --- a/py/objint.c +++ b/py/objint.c @@ -30,7 +30,7 @@ #include "py/parsenum.h" #include "py/smallint.h" -#include "py/objint.h" +#include "py/objint_impl.h" #include "py/objstr.h" #include "py/runtime.h" #include "py/binary.h" @@ -99,8 +99,8 @@ static mp_fp_as_int_class_t mp_classify_fp_as_int(mp_float_t val) { #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE e = u.i[MP_ENDIANNESS_LITTLE]; #endif -#define MP_FLOAT_SIGN_SHIFT_I32 ((MP_FLOAT_FRAC_BITS + MP_FLOAT_EXP_BITS) % 32) -#define MP_FLOAT_EXP_SHIFT_I32 (MP_FLOAT_FRAC_BITS % 32) + #define MP_FLOAT_SIGN_SHIFT_I32 ((MP_FLOAT_FRAC_BITS + MP_FLOAT_EXP_BITS) % 32) + #define MP_FLOAT_EXP_SHIFT_I32 (MP_FLOAT_FRAC_BITS % 32) if (e & (1U << MP_FLOAT_SIGN_SHIFT_I32)) { #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE @@ -207,10 +207,10 @@ static const uint8_t log_base2_floor[] = { 3, 3, 3, 3, 3, 3, 3, 4, /* if needed, these are the values for higher bases - 4, 4, 4, 4, - 4, 4, 4, 4, - 4, 4, 4, 4, - 4, 4, 4, 5 + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 5 */ }; @@ -309,74 +309,9 @@ char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_co return b; } -// CIRCUITPY-CHANGE: more thorough checking -#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE - -void mp_obj_int_buffer_overflow_check(mp_obj_t self_in, size_t nbytes, bool is_signed) { - if (is_signed) { - // self must be < 2**(bits - 1) - mp_obj_t edge = mp_binary_op(MP_BINARY_OP_LSHIFT, - mp_obj_new_int(1), - mp_obj_new_int(nbytes * 8 - 1)); - - if (mp_binary_op(MP_BINARY_OP_LESS, self_in, edge) == mp_const_true) { - // and >= -2**(bits - 1) - edge = mp_unary_op(MP_UNARY_OP_NEGATIVE, edge); - if (mp_binary_op(MP_BINARY_OP_MORE_EQUAL, self_in, edge) == mp_const_true) { - return; - } - } - } else { - // self must be >= 0 - if (mp_obj_int_sign(self_in) >= 0) { - // and < 2**(bits) - mp_obj_t edge = mp_binary_op(MP_BINARY_OP_LSHIFT, - mp_obj_new_int(1), - mp_obj_new_int(nbytes * 8)); - - if (mp_binary_op(MP_BINARY_OP_LESS, self_in, edge) == mp_const_true) { - return; - } - } - } - - mp_raise_OverflowError_varg(MP_ERROR_TEXT("value must fit in %d byte(s)"), nbytes); -} - -#endif // MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE - +// CIRCUITPY-CHANGE: also called from py/binary.c, so not static here. void mp_small_int_buffer_overflow_check(mp_int_t val, size_t nbytes, bool is_signed) { - // Fast path for zero. - if (val == 0) { - return; - } - - // Trying to store negative values in unsigned bytes falls through to failure. - if (is_signed || val >= 0) { - - if (nbytes >= sizeof(val)) { - // All non-negative N bit signed integers fit in an unsigned N bit integer. - // This case prevents shifting too far below. - return; - } - - if (is_signed) { - mp_int_t edge = ((mp_int_t)1 << (nbytes * 8 - 1)); - if (-edge <= val && val < edge) { - return; - } - // Out of range, fall through to failure. - } else { - // Unsigned. We already know val >= 0. - mp_int_t edge = ((mp_int_t)1 << (nbytes * 8)); - if (val < edge) { - return; - } - } - // Fall through to failure. - } - - mp_raise_OverflowError_varg(MP_ERROR_TEXT("value must fit in %d byte(s)"), nbytes); + mp_obj_small_int_buffer_overflow_check(val, nbytes, is_signed); } #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE @@ -447,6 +382,10 @@ mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) { return MP_OBJ_SMALL_INT_VALUE(self_in); } +void mp_obj_int_to_bytes(mp_obj_t self_in, size_t buf_len, byte *buf, bool big_endian, bool is_signed, bool overflow_check) { + mp_obj_small_int_to_bytes(MP_OBJ_SMALL_INT_VALUE(self_in), buf_len, buf, big_endian, is_signed, overflow_check); +} + #endif // MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE // This dispatcher function is expected to be independent of the implementation of long int @@ -456,7 +395,7 @@ mp_obj_t mp_obj_int_binary_op_extra_cases(mp_binary_op_t op, mp_obj_t lhs_in, mp // false acts as 0 return mp_binary_op(op, lhs_in, MP_OBJ_NEW_SMALL_INT(0)); } else if (rhs_in == mp_const_true) { - // true acts as 0 + // true acts as 1 return mp_binary_op(op, lhs_in, MP_OBJ_NEW_SMALL_INT(1)); } else if (op == MP_BINARY_OP_MULTIPLY) { if (mp_obj_is_str_or_bytes(rhs_in) || mp_obj_is_type(rhs_in, &mp_type_tuple) || mp_obj_is_type(rhs_in, &mp_type_list)) { @@ -539,49 +478,31 @@ static mp_obj_t int_from_bytes(size_t n_args, const mp_obj_t *pos_args, mp_map_t static MP_DEFINE_CONST_FUN_OBJ_KW(int_from_bytes_fun_obj, 2, int_from_bytes); static MP_DEFINE_CONST_CLASSMETHOD_OBJ(int_from_bytes_obj, MP_ROM_PTR(&int_from_bytes_fun_obj)); -// CIRCUITPY-CHANGE: supports signed static mp_obj_t int_to_bytes(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_length, ARG_byteorder, ARG_signed }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_length, MP_ARG_INT, {.u_int = 1} }, - // CIRCUITPY-CHANGE: not required and given a default value. - { MP_QSTR_byteorder, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_big)} }, + { MP_QSTR_length, MP_ARG_INT, { .u_int = 1 } }, + { MP_QSTR_byteorder, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_QSTR(MP_QSTR_big) } }, { MP_QSTR_signed, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_int_t len = args[ARG_length].u_int; - if (len < 0) { + mp_obj_t self = pos_args[0]; + + mp_int_t dlen = args[ARG_length].u_int; + if (dlen < 0) { mp_raise_ValueError(NULL); } - mp_obj_t self = pos_args[0]; - bool big_endian = args[ARG_byteorder].u_obj != MP_OBJ_NEW_QSTR(MP_QSTR_little); - bool signed_ = args[ARG_signed].u_bool; - vstr_t vstr; - vstr_init_len(&vstr, len); + vstr_init_len(&vstr, dlen); byte *data = (byte *)vstr.buf; - memset(data, 0, len); - #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE - if (!mp_obj_is_small_int(self)) { - mp_obj_int_buffer_overflow_check(self, len, signed_); - mp_obj_int_to_bytes_impl(self, big_endian, len, data); - } else - #endif - { - mp_int_t val = MP_OBJ_SMALL_INT_VALUE(self); - // Small int checking is separate, to be fast. - mp_small_int_buffer_overflow_check(val, len, signed_); - size_t l = MIN((size_t)len, sizeof(val)); - if (val < 0) { - // Sign extend negative numbers. - memset(data, -1, len); - } - mp_binary_set_int(l, big_endian, data + (big_endian ? (len - l) : 0), val); - } + bool big_endian = args[ARG_byteorder].u_obj != MP_OBJ_NEW_QSTR(MP_QSTR_little); + bool signed_ = args[ARG_signed].u_bool; + + mp_obj_int_to_bytes(self, dlen, data, big_endian, signed_, true); return mp_obj_new_bytes_from_vstr(&vstr); } diff --git a/py/objint.h b/py/objint.h index 92466bae4e2..94c53190d8b 100644 --- a/py/objint.h +++ b/py/objint.h @@ -53,19 +53,17 @@ char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_co int base, const char *prefix, char base_char, char comma); char *mp_obj_int_formatted_impl(char **buf, size_t *buf_size, size_t *fmt_size, mp_const_obj_t self_in, int base, const char *prefix, char base_char, char comma); -// CIRCUITPY-CHANGE -#if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE -void mp_obj_int_buffer_overflow_check(mp_obj_t self_in, size_t nbytes, bool is_signed); -#endif - -void mp_small_int_buffer_overflow_check(mp_int_t val, size_t nbytes, bool is_signed); mp_int_t mp_obj_int_hash(mp_obj_t self_in); // CIRCUITPY-CHANGE mp_obj_t mp_obj_int_bit_length_impl(mp_obj_t self_in); mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf); -// Returns true if 'self_in' fit into 'len' bytes of 'buf' without overflowing, 'buf' is truncated otherwise. -bool mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf); +// Write an integer to a byte sequence. +// If overflow_check is true, raises OverflowError if 'self_in' doesn't fit. If false, truncate to fit. +void mp_obj_int_to_bytes(mp_obj_t self_in, size_t buf_len, byte *buf, bool big_endian, bool is_signed, bool overflow_check); + +// CIRCUITPY-CHANGE: raises OverflowError if val does not fit in nbytes. +void mp_small_int_buffer_overflow_check(mp_int_t val, size_t nbytes, bool is_signed); int mp_obj_int_sign(mp_obj_t self_in); mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in); mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in); diff --git a/py/objint_impl.h b/py/objint_impl.h new file mode 100644 index 00000000000..b1af4d6244d --- /dev/null +++ b/py/objint_impl.h @@ -0,0 +1,98 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 Angus Gratton + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This header provides some inline implementations of functions used by multiple objint*.c + files. + + Implementations here should only be called by one of the objint*.c files. If + called from more than one place in a single firmware, place into objint.c and + define in objint.h + */ +#ifndef MICROPY_INCLUDED_PY_OBJINT_IMPL_H +#define MICROPY_INCLUDED_PY_OBJINT_IMPL_H + +#include "py/binary.h" +#include "py/objint.h" +#include "py/runtime.h" + +static void mp_obj_int_raise_to_bytes_overflow_error(size_t nbytes) { + mp_raise_msg_varg(&mp_type_OverflowError, MP_ERROR_TEXT("value would overflow a %d byte buffer"), nbytes); +} + +static void mp_obj_int_raise_unsigned_negative_overflow_error(void) { + mp_raise_msg_varg(&mp_type_OverflowError, MP_ERROR_TEXT("can't convert negative int to unsigned")); +} + +static void mp_obj_small_int_buffer_overflow_check(mp_int_t val, size_t nbytes, bool is_signed) { + // Fast path for zero. + if (val == 0) { + return; + } + + if (!is_signed && val < 0) { + // Trying to store negative values in unsigned bytes + mp_obj_int_raise_unsigned_negative_overflow_error(); + } + + if (nbytes >= sizeof(val)) { + // All N bit small integers fit in an unsigned N bit integer. + // This case prevents shifting too far below. + return; + } + + if (nbytes == 0) { + // Can't fit a non-negative value in 0 bytes (prevents negative left shift, below) + goto raise; + } + + if (is_signed) { + mp_int_t edge = ((mp_int_t)1 << (nbytes * 8 - 1)); + if (-edge <= val && val < edge) { + return; + } + // Out of range, fall through to raise. + } else { + // Unsigned. We already know val >= 0. + mp_int_t edge = ((mp_int_t)1 << (nbytes * 8)); + if (val < edge) { + return; + } + // Fall through to raise. + } + +raise: + mp_obj_int_raise_to_bytes_overflow_error(nbytes); +} + +static inline void mp_obj_small_int_to_bytes(mp_int_t val, size_t buf_len, byte *buf, bool big_endian, bool is_signed, bool overflow_check) { + if (overflow_check) { + mp_obj_small_int_buffer_overflow_check(val, buf_len, is_signed); + } + mp_binary_set_int(buf_len, buf, sizeof(val), val, big_endian); + +} + +#endif // MICROPY_INCLUDED_PY_OBJINT_IMPL_H diff --git a/py/objint_longlong.c b/py/objint_longlong.c index f24aa0cc186..49ac0b429fc 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -29,7 +29,7 @@ #include #include "py/smallint.h" -#include "py/objint.h" +#include "py/objint_impl.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_FLOAT @@ -73,42 +73,6 @@ mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf return mp_obj_new_int_from_ll(value); } -bool mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf) { - assert(mp_obj_is_exact_type(self_in, &mp_type_int)); - mp_obj_int_t *self = self_in; - long long val = self->val; - size_t slen; // Number of bytes to represent val - - // This logic has a twin in objint.c - if (val > 0) { - slen = (sizeof(long long) * 8 - mp_clzll(val) + 7) / 8; - } else if (val < -1) { - slen = (sizeof(long long) * 8 - mp_clzll(~val) + 8) / 8; - } else { - // clz of 0 is defined, so 0 and -1 map to 0 and 1 - slen = -val; - } - - if (slen > len) { - return false; // Would overflow - // TODO: Determine whether to copy and truncate, as some callers probably expect this...? - } - - if (big_endian) { - byte *b = buf + len; - while (b > buf) { - *--b = val; - val >>= 8; - } - } else { - for (; len > 0; --len) { - *buf++ = val; - val >>= 8; - } - } - return true; -} - int mp_obj_int_sign(mp_obj_t self_in) { mp_longint_impl_t val; if (mp_obj_is_small_int(self_in)) { @@ -240,7 +204,7 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i // negative shift not allowed mp_raise_ValueError(MP_ERROR_TEXT("negative shift count")); } - overflow = rhs_val >= (sizeof(long long) * MP_BITS_PER_BYTE) + overflow = rhs_val >= (long long)(sizeof(long long) * MP_BITS_PER_BYTE) || lhs_val > (LLONG_MAX >> rhs_val) || lhs_val < (LLONG_MIN >> rhs_val); result = (unsigned long long)lhs_val << rhs_val; @@ -373,4 +337,88 @@ mp_float_t mp_obj_int_as_float_impl(mp_obj_t self_in) { } #endif +// Same as the general mp_small_int_buffer_overflow_check() in objint_impl.h, but using 64-bit integers +static void longint_buffer_overflow_check(mp_longint_impl_t val, size_t nbytes, bool is_signed) { + // Fast path for zero. + if (val == 0) { + return; + } + + if (!is_signed && val < 0) { + // Trying to store negative values in unsigned bytes + mp_obj_int_raise_unsigned_negative_overflow_error(); + } + + if (nbytes >= sizeof(val)) { + // All non-negative N bit signed integers fit in an unsigned N bit integer. + // This case prevents shifting too far below. + return; + } + + if (nbytes == 0) { + // Can't fit a non-zero value in 0 bytes (prevents negative left shift below) + goto raise; + } + + if (is_signed) { + mp_longint_impl_t edge = 1LL << (nbytes * 8 - 1); + if (-edge <= val && val < edge) { + return; + } + // Out of range, fall through to failure. + } else { + // Unsigned. We already know val >= 0. + mp_longint_impl_t edge = 1LL << (nbytes * 8); + if (val < edge) { + return; + } + // Fall through to failure. + } + +raise: + mp_obj_int_raise_to_bytes_overflow_error(nbytes); +} + +static void longint_to_bytes(long long val, bool big_endian, size_t len, byte *buf) { + MP_STATIC_ASSERT(sizeof(mp_uint_t) == 4); + mp_uint_t lower = val; + mp_uint_t upper = (val >> 32); + + if (big_endian) { + if (len > 4) { + // write the least significant 4 bytes at the end + mp_binary_set_int(4, buf + len - 4, sizeof(lower), lower, true); + // write most significant bytes at the start, extending if necessary + mp_binary_set_int(len - 4, buf, sizeof(upper), upper, true); + } else { + mp_binary_set_int(len, buf, sizeof(lower), lower, true); + } + } else { + // write the least significant 4 bytes at the start + mp_binary_set_int(len > 4 ? len - 4 : len, buf, sizeof(lower), lower, false); + if (len > 4) { + // write the most significant bytes at the end, extending if necessary + mp_binary_set_int(len - 4, buf + 4, sizeof(upper), upper, false); + } + } +} + +void mp_obj_int_to_bytes(mp_obj_t self_in, size_t buf_len, byte *buf, bool big_endian, bool is_signed, bool overflow_check) { + mp_longint_impl_t val; + if (mp_obj_is_exact_type(self_in, &mp_type_int)) { + const mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); + val = self->val; + } else { + // self_in is either a smallint, or another type convertible to mp_int_t (i.e. bool) + val = mp_obj_get_int(self_in); + } + + // Note: to save code size we don't call mp_obj_small_int_to_bytes() here, + // as the longint implementation is very similar + if (overflow_check) { + longint_buffer_overflow_check(val, buf_len, is_signed); + } + longint_to_bytes(val, big_endian, buf_len, buf); +} + #endif diff --git a/py/objint_mpz.c b/py/objint_mpz.c index 1bede811a4a..f002dfe68a9 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -30,7 +30,7 @@ #include "py/parsenumbase.h" #include "py/smallint.h" -#include "py/objint.h" +#include "py/objint_impl.h" #include "py/runtime.h" #if MICROPY_PY_BUILTINS_FLOAT @@ -119,12 +119,6 @@ mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf return MP_OBJ_FROM_PTR(o); } -bool mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf) { - assert(mp_obj_is_exact_type(self_in, &mp_type_int)); - mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); - return mpz_as_bytes(&self->mpz, big_endian, self->mpz.neg, len, buf); -} - int mp_obj_int_sign(mp_obj_t self_in) { if (mp_obj_is_small_int(self_in)) { mp_int_t val = MP_OBJ_SMALL_INT_VALUE(self_in); @@ -483,4 +477,22 @@ mp_float_t mp_obj_int_as_float_impl(mp_obj_t self_in) { } #endif +void mp_obj_int_to_bytes(mp_obj_t self_in, size_t buf_len, byte *buf, bool big_endian, bool is_signed, bool overflow_check) { + if (mp_obj_is_exact_type(self_in, &mp_type_int)) { + const mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); + const mpz_t *mpz = &self->mpz; + if (overflow_check && !is_signed && mpz->neg) { + mp_obj_int_raise_unsigned_negative_overflow_error(); + } + if (!mpz_as_bytes(mpz, big_endian, is_signed, buf_len, buf) && overflow_check) { + mp_obj_int_raise_to_bytes_overflow_error(buf_len); + } + } else { + // self_in is either a smallint, or another type convertible to mp_int_t (i.e. bool) + mp_int_t val = mp_obj_get_int(self_in); + mp_obj_small_int_to_bytes(val, buf_len, buf, big_endian, is_signed, overflow_check); + } +} + + #endif diff --git a/py/objlist.c b/py/objlist.c index 3edf1e4244b..99060d895e0 100644 --- a/py/objlist.c +++ b/py/objlist.c @@ -39,8 +39,7 @@ static mp_obj_t list_pop(size_t n_args, const mp_obj_t *args); // TODO: Move to mpconfig.h #define LIST_MIN_ALLOC 4 -// CIRCUITPY-CHANGE: native_list() and other changes here for broadcom port -// https://github.com/adafruit/circuitpython/pull/5610 +// CIRCUITPY-CHANGE: native_list() supports list methods on subclasses of list. /******************************************************************************/ /* list */ diff --git a/py/objstr.c b/py/objstr.c index d8dd61d04af..1db72dc22f5 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -194,6 +194,79 @@ static void str_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t } } +#if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK && MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS +// Build a new string from data containing invalid UTF-8 or ASCII, either skipping the +// invalid bytes (errors=="ignore") or replacing them with U+FFFD +// (errors=="replace"). +static mp_obj_t str_from_invalid(mp_encoding_t encoding, const mp_obj_type_t *type, const byte *str_data, size_t str_len, qstr errors) { + bool do_replace = (errors == MP_QSTR_replace); + bool is_utf8 = (encoding == MP_ENCODING_UTF8); + vstr_t vstr; + vstr_init(&vstr, str_len); + const byte *p = str_data; + const byte *end = str_data + str_len; + + while (p < end) { + byte c = *p; + if (c < 0x80) { + // Valid ASCII + vstr_add_byte(&vstr, c); + p++; + } else if (is_utf8 && c >= 0xc0 && c < 0xf8) { + // Potential multi-byte sequence + uint8_t need = (0xe5 >> ((c >> 3) & 0x6)) & 3; + const byte *seq_start = p; + p++; + + // Check continuation bytes + uint8_t got = 0; + while (got < need && p < end && UTF8_IS_CONT(*p)) { + got++; + p++; + } + + if (got == need) { + // Valid complete sequence, decode and add the character + unichar ch = *seq_start & (0x7f >> need); + for (uint8_t i = 0; i < need; i++) { + ch = (ch << 6) | (seq_start[i + 1] & 0x3f); + } + vstr_add_char(&vstr, ch); + } else if (do_replace) { + // Invalid or incomplete sequence - replace with U+FFFD + vstr_add_char(&vstr, 0xFFFD); + } + // For 'ignore' mode, do nothing (skip invalid bytes) + } else if (do_replace) { + // Invalid start byte or non-ascii char - replace with U+FFFD + vstr_add_char(&vstr, 0xFFFD); + p++; + } else { + // Invalid start byte or non-ascii char - skip for 'ignore' mode + p++; + } + } + + return mp_obj_new_str_type_from_vstr(type, &vstr); +} +#endif // MICROPY_PY_BUILTINS_STR_UNICODE_CHECK && MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS + +#if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK +static mp_encoding_t parse_encoding_arg(qstr encoding) { + if (encoding == MP_QSTR_utf_hyphen_8 || encoding == MP_QSTR_utf8) { + return MP_ENCODING_UTF8; + } + if (encoding == MP_QSTR_ascii) { + return MP_ENCODING_ASCII; + } + #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE + mp_raise_type(&mp_type_LookupError); + #else + mp_raise_msg_varg(&mp_type_LookupError, MP_ERROR_TEXT("unknown encoding: %q"), encoding); + #endif +} +#endif + mp_obj_t mp_obj_str_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { #if MICROPY_CPYTHON_COMPAT if (n_kw != 0) { @@ -215,36 +288,61 @@ mp_obj_t mp_obj_str_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ return mp_obj_new_str_type_from_vstr(type, &vstr); } - default: // 2 or 3 args - // TODO: validate 2nd/3rd args + default: { // 2 or 3 args + // Extract the source data. + const byte *str_data; + size_t str_len; if (mp_obj_is_type(args[0], &mp_type_bytes)) { - GET_STR_DATA_LEN(args[0], str_data, str_len); - GET_STR_HASH(args[0], str_hash); - if (str_hash == 0) { - str_hash = qstr_compute_hash(str_data, str_len); + // Immutable bytes can be referenced directly (zero-copy); + GET_STR_DATA_LEN(args[0], bytes_data, bytes_len); + str_data = bytes_data; + str_len = bytes_len; + } else { + // any other buffer object is mutable sob its data must be copied. + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); + str_data = bufinfo.buf; + str_len = bufinfo.len; + } + + #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK + mp_encoding_t encoding = parse_encoding_arg(mp_obj_str_get_qstr(args[1])); + if (!unicode_encoding_check(encoding, str_data, str_len)) { + #if MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS + // Check if error handler is specified (3rd argument) + qstr errors = MP_QSTR_; // default to "" + if (n_args >= 3 && args[2] != mp_const_none) { + errors = mp_obj_str_get_qstr(args[2]); } - #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK - if (!utf8_check(str_data, str_len)) { - mp_raise_msg(&mp_type_UnicodeError, NULL); + if (errors == MP_QSTR_ignore || errors == MP_QSTR_replace) { + return str_from_invalid(encoding, type, str_data, str_len, errors); } - #endif + #endif // MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS + mp_raise_msg(&mp_type_UnicodeError, NULL); + } + #endif // MICROPY_PY_BUILTINS_STR_UNICODE_CHECK - // Check if a qstr with this data already exists - qstr q = qstr_find_strn((const char *)str_data, str_len); - if (q != MP_QSTRnull) { - return MP_OBJ_NEW_QSTR(q); - } + // Check if a qstr with this data already exists + qstr q = qstr_find_strn((const char *)str_data, str_len); + if (q != MP_QSTRnull) { + return MP_OBJ_NEW_QSTR(q); + } - mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(type, NULL, str_len)); - o->data = str_data; - o->hash = str_hash; - return MP_OBJ_FROM_PTR(o); - } else { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); - // This will utf-8 check the input. - return mp_obj_new_str(bufinfo.buf, bufinfo.len); + if (!mp_obj_is_type(args[0], &mp_type_bytes)) { + // Source is a mutable buffer: copy the data. + return mp_obj_new_str_copy(type, str_data, str_len); } + + // Source is immutable bytes: reference its data without copying. + GET_STR_HASH(args[0], str_hash); + if (str_hash == 0) { + str_hash = qstr_compute_hash(str_data, str_len); + } + mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(type, NULL, str_len)); + o->data = str_data; + o->hash = str_hash; + return MP_OBJ_FROM_PTR(o); + } } } @@ -275,11 +373,20 @@ static mp_obj_t bytes_make_new(const mp_obj_type_t *type_in, size_t n_args, size mp_raise_TypeError(MP_ERROR_TEXT("string argument without an encoding")); #endif } + GET_STR_DATA_LEN(args[0], str_data, str_len); GET_STR_HASH(args[0], str_hash); if (str_hash == 0) { str_hash = qstr_compute_hash(str_data, str_len); } + + #if MICROPY_PY_BUILTINS_STR_UNICODE_CHECK + mp_encoding_t encoding = parse_encoding_arg(mp_obj_str_get_qstr(args[1])); + if (encoding == MP_ENCODING_ASCII && !unicode_encoding_check(MP_ENCODING_ASCII, str_data, str_len)) { + mp_raise_msg(&mp_type_UnicodeError, NULL); + } + #endif + mp_obj_str_t *o = MP_OBJ_TO_PTR(mp_obj_new_str_copy(&mp_type_bytes, NULL, str_len)); o->data = str_data; o->hash = str_hash; @@ -787,11 +894,29 @@ static mp_obj_t str_finder(size_t n_args, const mp_obj_t *args, int direction, b const mp_obj_type_t *self_type = mp_obj_get_type(args[0]); check_is_str_or_bytes(args[0]); - // check argument type - str_check_arg_type(self_type, args[1]); - GET_STR_DATA_LEN(args[0], haystack, haystack_len); - GET_STR_DATA_LEN(args[1], needle, needle_len); + + mp_int_t val; + byte needle_data; + const byte *needle; + size_t needle_len; + if (self_type != &mp_type_str && mp_obj_get_int_maybe(args[1], &val)) { + // Allow {bytes/bytearray}.{find,index}(int). + #if MICROPY_FULL_CHECKS + if (val < 0 || val > 255) { + mp_raise_ValueError(MP_ERROR_TEXT("bytes value out of range")); + } + #endif + needle_data = val; + needle = &needle_data; + needle_len = 1; + } else { + // check argument type + str_check_arg_type(self_type, args[1]); + GET_STR_DATA_LEN(args[1], needle_tmp, needle_len_tmp); + needle = needle_tmp; + needle_len = needle_len_tmp; + } const byte *start = haystack; const byte *end = haystack + haystack_len; @@ -961,14 +1086,33 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rstrip_obj, 1, 2, str_rstrip); static mp_obj_t str_center(mp_obj_t str_in, mp_obj_t width_in) { GET_STR_DATA_LEN(str_in, str, str_len); mp_uint_t width = mp_obj_get_int(width_in); + + #if MICROPY_PY_BUILTINS_STR_UNICODE + // Get character count (not byte count) for proper Unicode handling + size_t char_len = utf8_charlen(str, str_len); + if (char_len >= width) { + return str_in; + } + // Calculate padding: width is in characters, need to convert to bytes for allocation + mp_uint_t padding_chars = width - char_len; + // Padding is always spaces (1 byte each), plus the original string bytes + mp_uint_t total_bytes = padding_chars + str_len; + #else + // Non-Unicode build: byte length equals character length if (str_len >= width) { return str_in; } + mp_uint_t total_bytes = width; + #endif // MICROPY_PY_BUILTINS_STR_UNICODE vstr_t vstr; - vstr_init_len(&vstr, width); - memset(vstr.buf, ' ', width); + vstr_init_len(&vstr, total_bytes); + memset(vstr.buf, ' ', total_bytes); + #if MICROPY_PY_BUILTINS_STR_UNICODE + int left = padding_chars / 2; + #else int left = (width - str_len) / 2; + #endif // MICROPY_PY_BUILTINS_STR_UNICODE memcpy(vstr.buf + left, str, str_len); return mp_obj_new_str_type_from_vstr(mp_obj_get_type(str_in), &vstr); } @@ -1029,6 +1173,23 @@ static MP_NORETURN void terse_str_format_value_error(void) { #define terse_str_format_value_error() #endif +// Print the character with the code point given by the integer object arg. +// Used by both the str.format and the modulo (%c) formatters. +static void mp_print_char(const mp_print_t *print, mp_obj_t arg, unsigned int flags, char fill, int width) { + #if MICROPY_FULL_CHECKS + mp_uint_t c = mp_obj_get_int(arg); + if (c >= 0x110000) { + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("char not in range(0x110000)")); + } + VSTR_FIXED(ch_vstr, 4); + vstr_add_char(&ch_vstr, c); + mp_print_strn(print, ch_vstr.buf, ch_vstr.len, flags, fill, width); + #else + char ch = mp_obj_get_int(arg); + mp_print_strn(print, &ch, 1, flags, fill, width); + #endif +} + static vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *arg_i, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { vstr_t vstr; mp_print_t print; @@ -1322,8 +1483,7 @@ static vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar continue; case 'c': { - char ch = mp_obj_get_int(arg); - mp_print_strn(&print, &ch, 1, flags, fill, width); + mp_print_char(&print, arg, flags, fill, width); continue; } @@ -1619,8 +1779,7 @@ static mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ } mp_print_strn(&print, s, 1, flags, ' ', width); } else if (arg_looks_integer(arg)) { - char ch = mp_obj_get_int(arg); - mp_print_strn(&print, &ch, 1, flags, ' ', width); + mp_print_char(&print, arg, flags, ' ', width); } else { mp_raise_TypeError(MP_ERROR_TEXT("%%c needs int or char")); } @@ -1982,6 +2141,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(str_islower_obj, str_islower); #if MICROPY_CPYTHON_COMPAT // These methods are superfluous in the presence of str() and bytes() // constructors. + // TODO: should accept kwargs too static mp_obj_t bytes_decode(size_t n_args, const mp_obj_t *args) { mp_obj_t new_args[2]; @@ -2312,7 +2472,7 @@ static mp_obj_t mp_obj_new_str_type_from_vstr(const mp_obj_type_t *type, vstr_t mp_obj_t mp_obj_new_str_from_vstr(vstr_t *vstr) { #if MICROPY_PY_BUILTINS_STR_UNICODE && MICROPY_PY_BUILTINS_STR_UNICODE_CHECK - if (!utf8_check((byte *)vstr->buf, vstr->len)) { + if (!unicode_encoding_check(MP_ENCODING_UTF8, (byte *)vstr->buf, vstr->len)) { mp_raise_msg(&mp_type_UnicodeError, NULL); } #endif // MICROPY_PY_BUILTINS_STR_UNICODE && MICROPY_PY_BUILTINS_STR_UNICODE_CHECK @@ -2332,7 +2492,7 @@ mp_obj_t mp_obj_new_bytes_from_vstr(vstr_t *vstr) { mp_obj_t mp_obj_new_str(const char *data, size_t len) { #if MICROPY_PY_BUILTINS_STR_UNICODE && MICROPY_PY_BUILTINS_STR_UNICODE_CHECK - if (!utf8_check((byte *)data, len)) { + if (!unicode_encoding_check(MP_ENCODING_UTF8, (byte *)data, len)) { mp_raise_msg(&mp_type_UnicodeError, NULL); } #endif diff --git a/py/objstrunicode.c b/py/objstrunicode.c index a158b912365..6dbdccb77fa 100644 --- a/py/objstrunicode.c +++ b/py/objstrunicode.c @@ -57,11 +57,11 @@ static void uni_print_quoted(const mp_print_t *print, const byte *str_data, uint mp_printf(print, "%c", quote_char); const byte *s = str_data, *top = str_data + str_len; while (s < top) { + const byte *seq_start = s; unichar ch; ch = utf8_get_char(s); - // CIRCUITPY-CHANGE: print printable Unicode chars - const byte *start = s; s = utf8_next_char(s); + size_t seq_len = s - seq_start; if (ch == quote_char) { mp_printf(print, "\\%c", quote_char); } else if (ch == '\\') { @@ -79,12 +79,15 @@ static void uni_print_quoted(const mp_print_t *print, const byte *str_data, uint mp_printf(print, "\\x%02x", ch); } else if ((0x2000 <= ch && ch <= 0x200f) || ch == 0x2028 || ch == 0x2029 || ch == 0xffff) { mp_printf(print, "\\u%04x", ch); - } else if (ch == 0x1ffff) { + } else if (ch >= 0xd800 && ch < 0xe000) { + // Surrogate (0xD800-0xDFFF) - output as \uXXXX escape. + mp_printf(print, "\\u%04x", ch); + } else if (ch == 0x1ffff || ch >= 0x110000) { + // Invalid - output as \UXXXXXXXX escape. mp_printf(print, "\\U%08x", ch); } else { - // Print the full character out. - int width = s - start; - mp_print_strn(print, (const char *)start, width, 0, ' ', width); + // CIRCUITPY-CHANGE: print printable Unicode chars out in full. + mp_print_strn(print, (const char *)seq_start, seq_len, 0, ' ', seq_len); } } mp_printf(print, "%c", quote_char); diff --git a/py/objtuple.c b/py/objtuple.c index fcfb8e0aa1f..6808f6c6e0a 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -104,20 +104,27 @@ static mp_obj_t mp_obj_tuple_make_new(const mp_obj_type_t *type_in, size_t n_arg } } +static mp_obj_tuple_t *tuple_subclass_helper(mp_obj_t obj) { + assert(obj != MP_OBJ_NULL); + const mp_obj_type_t *tuple_type = mp_obj_get_type(obj); + if (MP_OBJ_TYPE_GET_SLOT_OR_NULL(tuple_type, iter) != mp_obj_tuple_getiter) { + // Slow path for user subclasses + obj = mp_obj_cast_to_native_base(obj, MP_OBJ_FROM_PTR(&mp_type_tuple)); + if (obj == MP_OBJ_NULL) { + return NULL; + } + } + return MP_OBJ_TO_PTR(obj); +} + // Don't pass MP_BINARY_OP_NOT_EQUAL here static mp_obj_t tuple_cmp_helper(mp_uint_t op, mp_obj_t self_in, mp_obj_t another_in) { mp_check_self(mp_obj_is_tuple_compatible(self_in)); - const mp_obj_type_t *another_type = mp_obj_get_type(another_in); mp_obj_tuple_t *self = MP_OBJ_TO_PTR(self_in); - if (MP_OBJ_TYPE_GET_SLOT_OR_NULL(another_type, iter) != mp_obj_tuple_getiter) { - // Slow path for user subclasses - another_in = mp_obj_cast_to_native_base(another_in, MP_OBJ_FROM_PTR(&mp_type_tuple)); - if (another_in == MP_OBJ_NULL) { - return MP_OBJ_NULL; - } + mp_obj_tuple_t *another = tuple_subclass_helper(another_in); + if (!another) { + return MP_OBJ_NULL; } - mp_obj_tuple_t *another = MP_OBJ_TO_PTR(another_in); - return mp_obj_new_bool(mp_seq_cmp_objs(op, self->items, self->len, another->items, another->len)); } @@ -146,10 +153,10 @@ mp_obj_t mp_obj_tuple_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { switch (op) { case MP_BINARY_OP_ADD: case MP_BINARY_OP_INPLACE_ADD: { - if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(rhs)), MP_OBJ_FROM_PTR(&mp_type_tuple))) { + mp_obj_tuple_t *p = tuple_subclass_helper(rhs); + if (!p) { return MP_OBJ_NULL; // op not supported } - mp_obj_tuple_t *p = MP_OBJ_TO_PTR(rhs); mp_obj_tuple_t *s = MP_OBJ_TO_PTR(mp_obj_new_tuple(o->len + p->len, NULL)); mp_seq_cat(s->items, o->items, o->len, p->items, p->len, mp_obj_t); return MP_OBJ_FROM_PTR(s); diff --git a/py/parse.c b/py/parse.c index 36150ef468b..203527c782d 100644 --- a/py/parse.c +++ b/py/parse.c @@ -496,10 +496,10 @@ void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t ind uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); switch (MP_PARSE_NODE_LEAF_KIND(pn)) { case MP_PARSE_NODE_ID: - mp_printf(print, "id(%s)\n", qstr_str(arg)); + mp_printf(print, "id(%q)\n", (qstr)arg); break; case MP_PARSE_NODE_STRING: - mp_printf(print, "str(%s)\n", qstr_str(arg)); + mp_printf(print, "str(%q)\n", (qstr)arg); break; default: assert(MP_PARSE_NODE_LEAF_KIND(pn) == MP_PARSE_NODE_TOKEN); @@ -825,50 +825,54 @@ static bool fold_constants(parser_t *parser, uint8_t rule_id, size_t num_args) { if (!MP_PARSE_NODE_IS_NULL(pn1) && !(MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_expr_stmt_augassign) || MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_expr_stmt_assign_list))) { - // this node is of the form = + // this node is of the form = or : = mp_parse_node_t pn0 = peek_result(parser, 1); - if (MP_PARSE_NODE_IS_ID(pn0) - && MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_atom_expr_normal) - && MP_PARSE_NODE_IS_ID(((mp_parse_node_struct_t *)pn1)->nodes[0]) - && MP_PARSE_NODE_LEAF_ARG(((mp_parse_node_struct_t *)pn1)->nodes[0]) == MP_QSTR_const - && MP_PARSE_NODE_IS_STRUCT_KIND(((mp_parse_node_struct_t *)pn1)->nodes[1], RULE_trailer_paren) - ) { - // code to assign dynamic constants: id = const(value) - - // get the id - qstr id = MP_PARSE_NODE_LEAF_ARG(pn0); - - // get the value - mp_parse_node_t pn_value = ((mp_parse_node_struct_t *)((mp_parse_node_struct_t *)pn1)->nodes[1])->nodes[0]; - if (!mp_parse_node_is_const(pn_value)) { - mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, - MP_ERROR_TEXT("not a constant")); - mp_obj_exception_add_traceback(exc, parser->lexer->source_name, - ((mp_parse_node_struct_t *)pn1)->source_line, MP_QSTRnull); - nlr_raise(exc); - } - mp_obj_t value = mp_parse_node_convert_to_obj(pn_value); - - // store the value in the table of dynamic constants - mp_map_elem_t *elem = mp_map_lookup(&parser->consts, MP_OBJ_NEW_QSTR(id), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); - assert(elem->value == MP_OBJ_NULL); - elem->value = value; - - // If the constant starts with an underscore then treat it as a private - // variable and don't emit any code to store the value to the id. - if (qstr_str(id)[0] == '_') { - pop_result(parser); // pop const(value) - pop_result(parser); // pop id - push_result_rule(parser, 0, RULE_pass_stmt, 0); // replace with "pass" - return true; + if (MP_PARSE_NODE_IS_ID(pn0)) { + if (MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_annassign)) { + // extract annassign rhs + pn1 = ((mp_parse_node_struct_t *)pn1)->nodes[1]; } + if (MP_PARSE_NODE_IS_STRUCT_KIND(pn1, RULE_atom_expr_normal) + && MP_PARSE_NODE_IS_ID(((mp_parse_node_struct_t *)pn1)->nodes[0]) + && MP_PARSE_NODE_LEAF_ARG(((mp_parse_node_struct_t *)pn1)->nodes[0]) == MP_QSTR_const + && MP_PARSE_NODE_IS_STRUCT_KIND(((mp_parse_node_struct_t *)pn1)->nodes[1], RULE_trailer_paren)) { + // code to assign dynamic constants: id = const(value) + + // get the id + qstr id = MP_PARSE_NODE_LEAF_ARG(pn0); + + // get the value + mp_parse_node_t pn_value = ((mp_parse_node_struct_t *)((mp_parse_node_struct_t *)pn1)->nodes[1])->nodes[0]; + if (!mp_parse_node_is_const(pn_value)) { + mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, + MP_ERROR_TEXT("not a constant")); + mp_obj_exception_add_traceback(exc, parser->lexer->source_name, + ((mp_parse_node_struct_t *)pn1)->source_line, MP_QSTRnull); + nlr_raise(exc); + } + mp_obj_t value = mp_parse_node_convert_to_obj(pn_value); + + // store the value in the table of dynamic constants + mp_map_elem_t *elem = mp_map_lookup(&parser->consts, MP_OBJ_NEW_QSTR(id), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + assert(elem->value == MP_OBJ_NULL); + elem->value = value; + + // If the constant starts with an underscore then treat it as a private + // variable and don't emit any code to store the value to the id. + if (qstr_str(id)[0] == '_') { + pop_result(parser); // pop const(value) + pop_result(parser); // pop id + push_result_rule(parser, 0, RULE_pass_stmt, 0); // replace with "pass" + return true; + } - // replace const(value) with value - pop_result(parser); - push_result_node(parser, pn_value); + // replace const(value) with value + pop_result(parser); + push_result_node(parser, pn_value); - // finished folding this assignment, but we still want it to be part of the tree - return false; + // finished folding this assignment, but we still want it to be part of the tree + return false; + } } } return false; diff --git a/py/persistentcode.c b/py/persistentcode.c index 7fea1465975..7542a97f000 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -569,9 +569,8 @@ static void mp_print_bytes(mp_print_t *print, const byte *data, size_t len) { print->print_strn(print->data, (const char *)data, len); } -#define BYTES_FOR_INT ((MP_BYTES_PER_OBJ_WORD * 8 + 6) / 7) static void mp_print_uint(mp_print_t *print, size_t n) { - byte buf[BYTES_FOR_INT]; + byte buf[MP_ENCODE_UINT_MAX_BYTES]; byte *p = buf + sizeof(buf); *--p = n & 0x7f; n >>= 7; diff --git a/py/persistentcode.h b/py/persistentcode.h index 9a9d8605641..4dbdacafd3e 100644 --- a/py/persistentcode.h +++ b/py/persistentcode.h @@ -53,38 +53,50 @@ #define MPY_FEATURE_DECODE_ARCH(feat) (((feat) >> 2) & 0x2F) // Define the host architecture +// CIRCUITPY-CHANGE: MPY_FEATURE_ARCH_NAME, the mpy-cross -march name of the host architecture #if MICROPY_PERSISTENT_CODE_LOAD_NATIVE #if defined(__i386__) || defined(_M_IX86) #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X86) + #define MPY_FEATURE_ARCH_NAME "x86" #elif defined(__x86_64__) || defined(_M_X64) #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X64) + #define MPY_FEATURE_ARCH_NAME "x64" #elif defined(__thumb2__) || defined(__thumb__) #if defined(__thumb2__) #if defined(__ARM_FP) && (__ARM_FP & 8) == 8 #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EMDP) + #define MPY_FEATURE_ARCH_NAME "armv7emdp" #elif defined(__ARM_FP) && (__ARM_FP & 4) == 4 #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EMSP) + #define MPY_FEATURE_ARCH_NAME "armv7emsp" #else #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV7EM) + #define MPY_FEATURE_ARCH_NAME "armv7em" #endif #else #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6M) + #define MPY_FEATURE_ARCH_NAME "armv6m" #endif #define MPY_FEATURE_ARCH_TEST(x) (MP_NATIVE_ARCH_ARMV6M <= (x) && (x) <= MPY_FEATURE_ARCH) #elif defined(__arm__) #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_ARMV6) + #define MPY_FEATURE_ARCH_NAME "armv6" #elif defined(__xtensa__) #include #if XCHAL_HAVE_WINDOWED #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSAWIN) + #define MPY_FEATURE_ARCH_NAME "xtensawin" #else #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_XTENSA) + #define MPY_FEATURE_ARCH_NAME "xtensa" #endif #elif defined(__riscv) #if __riscv_xlen == 32 #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_RV32IMC) + #define MPY_FEATURE_ARCH_NAME "rv32imc" #elif __riscv_xlen == 64 #define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_RV64IMC) + #define MPY_FEATURE_ARCH_NAME "rv64imc" #else #error "Unsupported RISC-V architecture." #endif diff --git a/py/py.cmake b/py/py.cmake index 6888c170214..9d4fa15bd41 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -4,6 +4,12 @@ set(MICROPY_PY_DIR "${MICROPY_DIR}/py") list(APPEND MICROPY_INC_CORE "${MICROPY_DIR}") +# Set MICROPY_LIB_DIR default if not already set by the port. +# This needs to happen before usermod.cmake is included (for c_module() in manifests). +if(NOT MICROPY_LIB_DIR) + set(MICROPY_LIB_DIR ${MICROPY_DIR}/lib/micropython-lib) +endif() + # All py/ source files set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/argcheck.c @@ -61,6 +67,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/nativeglue.c ${MICROPY_PY_DIR}/nlr.c ${MICROPY_PY_DIR}/nlraarch64.c + ${MICROPY_PY_DIR}/nlrloong64.c ${MICROPY_PY_DIR}/nlrmips.c ${MICROPY_PY_DIR}/nlrpowerpc.c ${MICROPY_PY_DIR}/nlrrv32.c diff --git a/py/py.mk b/py/py.mk index f5365ed6f78..27d0a4bb34b 100644 --- a/py/py.mk +++ b/py/py.mk @@ -24,21 +24,15 @@ QSTR_GLOBAL_REQUIREMENTS += $(HEADER_BUILD)/mpversion.h # some code is performance bottleneck and compiled with other optimization options CSUPEROPT = -O3 -# Enable building 32-bit code on 64-bit host. -ifeq ($(MICROPY_FORCE_32BIT),1) -CC += -m32 -CXX += -m32 -LD += -m32 -endif - # External modules written in C. + +# Process manifest for C module extraction (appends to USER_C_MODULES). +include $(TOP)/py/manifest.mk + ifneq ($(USER_C_MODULES),) # pre-define USERMOD variables as expanded so that variables are immediate # expanded as they're added to them -# Confirm the provided path exists, show abspath if not to make it clearer to fix. -$(if $(wildcard $(USER_C_MODULES)/.),,$(error USER_C_MODULES doesn't exist: $(abspath $(USER_C_MODULES)))) - # C/C++ files that are included in the QSTR/module build SRC_USERMOD_C := SRC_USERMOD_CXX := @@ -50,34 +44,65 @@ SRC_USERMOD_LIB_ASM := CFLAGS_USERMOD := CXXFLAGS_USERMOD := LDFLAGS_USERMOD := +LIBS_USERMOD := # Backwards compatibility with older user c modules that set SRC_USERMOD # added to SRC_USERMOD_C below SRC_USERMOD := -$(foreach module, $(wildcard $(USER_C_MODULES)/*/micropython.mk), \ - $(eval USERMOD_DIR = $(patsubst %/,%,$(dir $(module))))\ - $(info Including User C Module from $(USERMOD_DIR))\ - $(eval include $(module))\ +# For each C module directory, scan for micropython.mk and include it. +# Accumulate USERMOD_DIRS so we can later strip each module's parent path +# from its source files while preserving the module basename in the build +# tree (e.g. build/cexample/foo.o, not build/foo.o, so files that share a +# name across modules don't collide on the same .o target). +USERMOD_DIRS := +$(foreach _UDIR, $(USER_C_MODULES), \ + $(if $(wildcard $(_UDIR)/.),,$(error USER_C_MODULES path doesn't exist: $(abspath $(_UDIR)))) \ + $(foreach module, $(wildcard $(_UDIR)/micropython.mk) $(wildcard $(_UDIR)/*/micropython.mk), \ + $(eval USERMOD_DIR = $(patsubst %/,%,$(dir $(module))))\ + $(eval USERMOD_DIRS += $(USERMOD_DIR))\ + $(info Including User C Module from $(USERMOD_DIR))\ + $(eval include $(module))\ + )\ ) SRC_USERMOD_C += $(SRC_USERMOD) -SRC_USERMOD_PATHFIX_C += $(patsubst $(USER_C_MODULES)/%.c,%.c,$(SRC_USERMOD_C)) -SRC_USERMOD_PATHFIX_CXX += $(patsubst $(USER_C_MODULES)/%.cpp,%.cpp,$(SRC_USERMOD_CXX)) -SRC_USERMOD_PATHFIX_LIB_C += $(patsubst $(USER_C_MODULES)/%.c,%.c,$(SRC_USERMOD_LIB_C)) -SRC_USERMOD_PATHFIX_LIB_CXX += $(patsubst $(USER_C_MODULES)/%.cpp,%.cpp,$(SRC_USERMOD_LIB_CXX)) -SRC_USERMOD_PATHFIX_LIB_ASM += $(patsubst $(USER_C_MODULES)/%.S,%.S,$(SRC_USERMOD_LIB_ASM)) +# Strip each USERMOD_DIR's leading path from source files, keeping the module +# basename as the leading directory so build outputs land at +# BUILD//.o. Uses per-directory patsubst on the paths as-is +# rather than $(abspath) so a parent directory containing whitespace doesn't +# break word-based patsubst. +SRC_USERMOD_PATHFIX_C := $(SRC_USERMOD_C) +SRC_USERMOD_PATHFIX_CXX := $(SRC_USERMOD_CXX) +SRC_USERMOD_PATHFIX_LIB_C := $(SRC_USERMOD_LIB_C) +SRC_USERMOD_PATHFIX_LIB_CXX := $(SRC_USERMOD_LIB_CXX) +SRC_USERMOD_PATHFIX_LIB_ASM := $(SRC_USERMOD_LIB_ASM) +USERMOD_DIRS := $(sort $(USERMOD_DIRS)) +$(foreach _MDIR, $(USERMOD_DIRS), \ + $(eval _MBASE := $(notdir $(_MDIR))) \ + $(eval SRC_USERMOD_PATHFIX_C := $$(patsubst $(_MDIR)/%,$(_MBASE)/%,$$(SRC_USERMOD_PATHFIX_C))) \ + $(eval SRC_USERMOD_PATHFIX_CXX := $$(patsubst $(_MDIR)/%,$(_MBASE)/%,$$(SRC_USERMOD_PATHFIX_CXX))) \ + $(eval SRC_USERMOD_PATHFIX_LIB_C := $$(patsubst $(_MDIR)/%,$(_MBASE)/%,$$(SRC_USERMOD_PATHFIX_LIB_C))) \ + $(eval SRC_USERMOD_PATHFIX_LIB_CXX := $$(patsubst $(_MDIR)/%,$(_MBASE)/%,$$(SRC_USERMOD_PATHFIX_LIB_CXX))) \ + $(eval SRC_USERMOD_PATHFIX_LIB_ASM := $$(patsubst $(_MDIR)/%,$(_MBASE)/%,$$(SRC_USERMOD_PATHFIX_LIB_ASM))) \ +) + +# Parent directories of each USERMOD_DIR, added to vpath below so paths like +# cexample/foo.c (produced by the patsubst above) resolve to source files. +USERMOD_DIR_PARENTS := $(sort $(foreach _MDIR, $(USERMOD_DIRS), $(patsubst %/,%,$(dir $(_MDIR))))) CFLAGS += $(CFLAGS_USERMOD) CXXFLAGS += $(CXXFLAGS_USERMOD) LDFLAGS += $(LDFLAGS_USERMOD) +LIBS += $(LIBS_USERMOD) SRC_QSTR += $(SRC_USERMOD_PATHFIX_C) $(SRC_USERMOD_PATHFIX_CXX) PY_O += $(addprefix $(BUILD)/, $(SRC_USERMOD_PATHFIX_C:.c=.o)) PY_O += $(addprefix $(BUILD)/, $(SRC_USERMOD_PATHFIX_CXX:.cpp=.o)) PY_O += $(addprefix $(BUILD)/, $(SRC_USERMOD_PATHFIX_LIB_C:.c=.o)) PY_O += $(addprefix $(BUILD)/, $(SRC_USERMOD_PATHFIX_LIB_CXX:.cpp=.o)) +PY_O += $(addprefix $(BUILD)/, $(SRC_USERMOD_PATHFIX_LIB_ASM:.S=.o)) endif # USER_C_MODULES # CIRCUITPY-CHANGE @@ -89,7 +114,9 @@ SRC_QSTR += $(ULAB_SRC_PATHFIX) CFLAGS_MOD += -DCIRCUITPY_ULAB=1 -DMODULE_ULAB_ENABLED=1 -DULAB_HAS_USER_MODULE=0 -iquote $(TOP)/extmod/ulab/code $(BUILD)/extmod/ulab/code/%.o: CFLAGS += -Wno-missing-declarations -Wno-missing-prototypes -Wno-unused-parameter -Wno-float-equal -Wno-sign-compare -Wno-cast-align -Wno-shadow -DCIRCUITPY ifeq ($(CIRCUITPY_ULAB_OPTIMIZE_SIZE),1) -$(BUILD)/extmod/ulab/code/%.o: CFLAGS += -Os +# Function-pointer dispatch for ndarray binary operators saves about 4 kB of +# flash at the cost of roughly 1.5x slower element-wise arithmetic. +$(BUILD)/extmod/ulab/code/%.o: CFLAGS += -Os -DNDARRAY_BINARY_USES_FUN_POINTER=1 endif # CIRCUITPY_ULAB_OPTIMIZE_SIZE endif # CIRCUITPY_ULAB @@ -107,6 +134,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ nlrxtensa.o \ nlrrv32.o \ nlrrv64.o \ + nlrloong64.o \ nlrsetjmp.o \ malloc.o \ gc.o \ @@ -277,6 +305,14 @@ $(HEADER_BUILD)/compressed.data.h: $(HEADER_BUILD)/compressed.collected $(HEADER_BUILD)/$(TRANSLATION).mo: $(TOP)/locale/$(TRANSLATION).po | $(HEADER_BUILD) $(Q)$(PYTHON) $(TOP)/tools/msgfmt.py -o $@ $^ +# Records which TRANSLATION the generated header was built for. It is rewritten +# only when the language changes, so that building another language in the +# same build directory regenerates compressed_translations.generated.h and +# translations-*.c instead of linking one language's strings against another +# language's tables. +$(HEADER_BUILD)/translation.stamp: FORCE | $(HEADER_BUILD) + $(Q)if [ "$$(cat $@ 2>/dev/null)" != "$(TRANSLATION)" ]; then echo "$(TRANSLATION)" > $@; fi + # translations-*.c is generated as a side-effect of building compressed_translations.generated.h # Specifying both in a single rule actually causes the rule to be run twice! # This alternative makes it run just once. @@ -285,7 +321,7 @@ $(HEADER_BUILD)/$(TRANSLATION).mo: $(TOP)/locale/$(TRANSLATION).po | $(HEADER_BU $(PY_BUILD)/translations-$(TRANSLATION).c: $(HEADER_BUILD)/compressed_translations.generated.h @true -$(HEADER_BUILD)/compressed_translations.generated.h: $(PY_SRC)/maketranslationdata.py $(HEADER_BUILD)/$(TRANSLATION).mo $(HEADER_BUILD)/qstrdefs.generated.h +$(HEADER_BUILD)/compressed_translations.generated.h: $(PY_SRC)/maketranslationdata.py $(HEADER_BUILD)/$(TRANSLATION).mo $(HEADER_BUILD)/qstrdefs.generated.h $(HEADER_BUILD)/translation.stamp $(STEPECHO) "GEN $@" $(Q)mkdir -p $(PY_BUILD) $(Q)$(PYTHON) $(PY_SRC)/maketranslationdata.py --compression_filename $(HEADER_BUILD)/compressed_translations.generated.h --translation $(HEADER_BUILD)/$(TRANSLATION).mo --translation_filename $(PY_BUILD)/translations-$(TRANSLATION).c --qstrdefs_filename $(HEADER_BUILD)/qstrdefs.generated.h --compression_level $(CIRCUITPY_MESSAGE_COMPRESSION_LEVEL) $(HEADER_BUILD)/qstrdefs.preprocessed.h diff --git a/py/qstr.c b/py/qstr.c index ab9ff4dd67d..07caa869a5b 100644 --- a/py/qstr.c +++ b/py/qstr.c @@ -84,8 +84,9 @@ size_t qstr_compute_hash(const byte *data, size_t len) { #if MICROPY_QSTR_BYTES_IN_HASH const qstr_hash_t mp_qstr_const_hashes_static[] = { #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) hash, -#define QDEF1(id, hash, len, str) +// CIRCUITPY-CHANGE: QDEF has an offset argument +#define QDEF0(id, hash, len, offset, str) hash, +#define QDEF1(id, hash, len, offset, str) // CIRCUITPY-CHANGE: translations #define TRANSLATION(id, length, compressed ...) #include "genhdr/qstrdefs.generated.h" @@ -97,18 +98,38 @@ const qstr_hash_t mp_qstr_const_hashes_static[] = { }; #endif -const qstr_len_t mp_qstr_const_lengths_static[] = { +// CIRCUITPY-CHANGE: The ROM pools store their strings in one NUL-separated blob +// per pool instead of a pointer array plus a length array. The blob is built by +// the preprocessor concatenating each QDEF's string literal followed by "\0", in +// file order; makeqstrdata.py computes the matching offsets, which are emitted as +// the QDEF offset argument. The offsets array has one extra trailing entry, the +// total blob length, so that qstr_len() is a difference of adjacent offsets. The +// string literal's own implicit NUL accounts for the "- 1" in sizeof() below. +const char mp_qstr_const_blob_static[] = #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) len, -#define QDEF1(id, hash, len, str) -// CIRCUITPY-CHANGE: translations +#define QDEF0(id, hash, len, offset, str) str "\0" +#define QDEF1(id, hash, len, offset, str) +#define TRANSLATION(id, length, compressed ...) + #include "genhdr/qstrdefs.generated.h" +#undef QDEF0 +#undef QDEF1 +#undef TRANSLATION + #else + "" + #endif +; + +const qstr_offset_t mp_qstr_const_offsets_static[] = { + #ifndef NO_QSTR +#define QDEF0(id, hash, len, offset, str) offset, +#define QDEF1(id, hash, len, offset, str) #define TRANSLATION(id, length, compressed ...) #include "genhdr/qstrdefs.generated.h" #undef QDEF0 #undef QDEF1 -// CIRCUITPY-CHANGE: translations #undef TRANSLATION #endif + sizeof(mp_qstr_const_blob_static) - 1, }; const qstr_pool_t mp_qstr_const_pool_static = { @@ -116,24 +137,14 @@ const qstr_pool_t mp_qstr_const_pool_static = { 0, // no previous pool false, // is_sorted MICROPY_ALLOC_QSTR_ENTRIES_INIT, - MP_QSTRnumber_of_static, // corresponds to number of strings in array just below + MP_QSTRnumber_of_static, // corresponds to number of strings in the blob #if MICROPY_QSTR_BYTES_IN_HASH (qstr_hash_t *)mp_qstr_const_hashes_static, #endif - (qstr_len_t *)mp_qstr_const_lengths_static, - { - #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) str, -#define QDEF1(id, hash, len, str) -// CIRCUITPY-CHANGE: translations -#define TRANSLATION(id, length, compressed ...) - #include "genhdr/qstrdefs.generated.h" -#undef QDEF0 -#undef QDEF1 -// CIRCUITPY-CHANGE: translations -#undef TRANSLATION - #endif - }, + // CIRCUITPY-CHANGE: strings live in the blob; no lengths array or qstrs[] + NULL, // lengths + mp_qstr_const_blob_static, + mp_qstr_const_offsets_static, }; // The next pool is the remainder of the qstrs defined in the firmware. This @@ -141,8 +152,9 @@ const qstr_pool_t mp_qstr_const_pool_static = { #if MICROPY_QSTR_BYTES_IN_HASH const qstr_hash_t mp_qstr_const_hashes[] = { #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) -#define QDEF1(id, hash, len, str) hash, +// CIRCUITPY-CHANGE: QDEF has an offset argument +#define QDEF0(id, hash, len, offset, str) +#define QDEF1(id, hash, len, offset, str) hash, // CIRCUITPY-CHANGE: translations #define TRANSLATION(id, length, compressed ...) #include "genhdr/qstrdefs.generated.h" @@ -154,18 +166,32 @@ const qstr_hash_t mp_qstr_const_hashes[] = { }; #endif -const qstr_len_t mp_qstr_const_lengths[] = { +// CIRCUITPY-CHANGE: blob and offsets, see above +const char mp_qstr_const_blob[] = #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) -#define QDEF1(id, hash, len, str) len, -// CIRCUITPY-CHANGE: translations +#define QDEF0(id, hash, len, offset, str) +#define QDEF1(id, hash, len, offset, str) str "\0" +#define TRANSLATION(id, length, compressed ...) + #include "genhdr/qstrdefs.generated.h" +#undef QDEF0 +#undef QDEF1 +#undef TRANSLATION + #else + "" + #endif +; + +const qstr_offset_t mp_qstr_const_offsets[] = { + #ifndef NO_QSTR +#define QDEF0(id, hash, len, offset, str) +#define QDEF1(id, hash, len, offset, str) offset, #define TRANSLATION(id, length, compressed ...) #include "genhdr/qstrdefs.generated.h" #undef QDEF0 #undef QDEF1 -// CIRCUITPY-CHANGE: translations #undef TRANSLATION #endif + sizeof(mp_qstr_const_blob) - 1, }; const qstr_pool_t mp_qstr_const_pool = { @@ -173,24 +199,14 @@ const qstr_pool_t mp_qstr_const_pool = { MP_QSTRnumber_of_static, true, // is_sorted MICROPY_ALLOC_QSTR_ENTRIES_INIT, - MP_QSTRnumber_of - MP_QSTRnumber_of_static, // corresponds to number of strings in array just below + MP_QSTRnumber_of - MP_QSTRnumber_of_static, // corresponds to number of strings in the blob #if MICROPY_QSTR_BYTES_IN_HASH (qstr_hash_t *)mp_qstr_const_hashes, #endif - (qstr_len_t *)mp_qstr_const_lengths, - { - #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) -#define QDEF1(id, hash, len, str) str, -// CIRCUITPY-CHANGE: translations -#define TRANSLATION(id, length, compressed ...) - #include "genhdr/qstrdefs.generated.h" -#undef QDEF0 -#undef QDEF1 -// CIRCUITPY-CHANGE: translations -#undef TRANSLATION - #endif - }, + // CIRCUITPY-CHANGE: strings live in the blob; no lengths array or qstrs[] + NULL, // lengths + mp_qstr_const_blob, + mp_qstr_const_offsets, }; // If frozen code is enabled, then there is an additional, sorted, ROM pool @@ -228,6 +244,22 @@ static const qstr_pool_t *find_qstr(qstr *q) { return pool; } +// CIRCUITPY-CHANGE: ROM pools keep their strings in a blob addressed by offsets; +// runtime pools keep a pointer array and a length array. See qstr_pool_t. +static inline const char *pool_str(const qstr_pool_t *pool, size_t at) { + if (pool->blob != NULL) { + return pool->blob + pool->offsets[at]; + } + return pool->qstrs[at]; +} + +static inline size_t pool_len(const qstr_pool_t *pool, size_t at) { + if (pool->blob != NULL) { + return pool->offsets[at + 1] - pool->offsets[at] - 1; + } + return pool->lengths[at]; +} + // qstr_mutex must be taken while in this function static qstr qstr_add(mp_uint_t len, const char *q_ptr) { #if MICROPY_QSTR_BYTES_IN_HASH @@ -267,6 +299,9 @@ static qstr qstr_add(mp_uint_t len, const char *q_ptr) { #else pool->lengths = (qstr_len_t *)(pool->qstrs + new_alloc); #endif + // CIRCUITPY-CHANGE: runtime pools use qstrs[] and lengths, not a blob + pool->blob = NULL; + pool->offsets = NULL; pool->prev = MP_STATE_VM(last_pool); pool->total_prev_len = MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len; pool->alloc = new_alloc; @@ -308,7 +343,8 @@ qstr qstr_find_strn(const char *str, size_t str_len) { if (pool->is_sorted) { while (high - low > 1) { size_t mid = (low + high) / 2; - int cmp = strncmp(str, pool->qstrs[mid], str_len); + // CIRCUITPY-CHANGE: pool accessor + int cmp = strncmp(str, pool_str(pool, mid), str_len); if (cmp <= 0) { high = mid; } else { @@ -323,8 +359,9 @@ qstr qstr_find_strn(const char *str, size_t str_len) { #if MICROPY_QSTR_BYTES_IN_HASH pool->hashes[at] == str_hash && #endif - pool->lengths[at] == str_len - && memcmp(pool->qstrs[at], str, str_len) == 0) { + // CIRCUITPY-CHANGE: pool accessors + pool_len(pool, at) == str_len + && memcmp(pool_str(pool, at), str, str_len) == 0) { return pool->total_prev_len + at; } } @@ -425,24 +462,28 @@ mp_uint_t qstr_hash(qstr q) { #if MICROPY_QSTR_BYTES_IN_HASH return pool->hashes[q]; #else - return qstr_compute_hash((byte *)pool->qstrs[q], pool->lengths[q]); + // CIRCUITPY-CHANGE: pool accessors + return qstr_compute_hash((byte *)pool_str(pool, q), pool_len(pool, q)); #endif } size_t qstr_len(qstr q) { const qstr_pool_t *pool = find_qstr(&q); - return pool->lengths[q]; + // CIRCUITPY-CHANGE: pool accessor + return pool_len(pool, q); } const char *qstr_str(qstr q) { const qstr_pool_t *pool = find_qstr(&q); - return pool->qstrs[q]; + // CIRCUITPY-CHANGE: pool accessor + return pool_str(pool, q); } const byte *qstr_data(qstr q, size_t *len) { const qstr_pool_t *pool = find_qstr(&q); - *len = pool->lengths[q]; - return (byte *)pool->qstrs[q]; + // CIRCUITPY-CHANGE: pool accessors + *len = pool_len(pool, q); + return (byte *)pool_str(pool, q); } void qstr_pool_info(size_t *n_pool, size_t *n_qstr, size_t *n_str_data_bytes, size_t *n_total_bytes) { diff --git a/py/qstr.h b/py/qstr.h index 7dc04a8accb..6c6e1a14ff0 100644 --- a/py/qstr.h +++ b/py/qstr.h @@ -38,8 +38,9 @@ // first entry in enum will be MP_QSTRnull=0, which indicates invalid/no qstr enum { #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) id, -#define QDEF1(id, hash, len, str) +// CIRCUITPY-CHANGE: QDEF carries the string's offset within its pool's blob +#define QDEF0(id, hash, len, offset, str) id, +#define QDEF1(id, hash, len, offset, str) // CIRCUITPY-CHANGE #define TRANSLATION(english_id, number) #include "genhdr/qstrdefs.generated.h" @@ -50,8 +51,9 @@ enum { MP_QSTRstart_of_main = MP_QSTRnumber_of_static - 1, // unused but shifts the enum counter back one #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) -#define QDEF1(id, hash, len, str) id, +// CIRCUITPY-CHANGE: QDEF carries the string's offset within its pool's blob +#define QDEF0(id, hash, len, offset, str) +#define QDEF1(id, hash, len, offset, str) id, #define TRANSLATION(english_id, number) #include "genhdr/qstrdefs.generated.h" #undef QDEF0 @@ -82,6 +84,9 @@ typedef uint16_t qstr_len_t; #error unimplemented qstr length decoding #endif +// CIRCUITPY-CHANGE: offset of a string within a ROM pool's blob +typedef uint16_t qstr_offset_t; + typedef struct _qstr_pool_t { const struct _qstr_pool_t *prev; size_t total_prev_len : (8 * sizeof(size_t) - 1); @@ -92,6 +97,12 @@ typedef struct _qstr_pool_t { qstr_hash_t *hashes; #endif qstr_len_t *lengths; + // CIRCUITPY-CHANGE: ROM pools store their strings in one NUL-separated blob. + // When blob is non-NULL, string i is at blob + offsets[i], its length is + // offsets[i + 1] - offsets[i] - 1 (offsets has len + 1 entries), and lengths + // and qstrs[] are unused. Runtime pools have blob == NULL and use lengths and qstrs[]. + const char *blob; + const qstr_offset_t *offsets; const char *qstrs[]; } qstr_pool_t; diff --git a/py/runtime.c b/py/runtime.c index 51d536efd86..62a7262fee5 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -116,6 +116,9 @@ void mp_init(void) { #if MICROPY_EMIT_NATIVE MP_STATE_VM(default_emit_opt) = MP_EMIT_OPT_NONE; #endif + #if MICROPY_DEBUG_PRINTERS + MP_STATE_VM(mp_verbose_flag) = 0; + #endif #endif // init global module dict @@ -1696,8 +1699,7 @@ void mp_import_all(mp_obj_t module) { mp_obj_t dest[2]; #if MICROPY_MODULE___ALL__ - - mp_load_method_maybe(module, MP_QSTR___all__, dest); + mp_load_method_protected(module, MP_QSTR___all__, dest, false); if (dest[0] != MP_OBJ_NULL) { // When __all__ is defined, we must explicitly load all specified // symbols, possibly invoking the module __getattr__ function diff --git a/py/showbc.c b/py/showbc.c index 792fccd0133..68e2f33138c 100644 --- a/py/showbc.c +++ b/py/showbc.c @@ -99,8 +99,8 @@ void mp_bytecode_print(const mp_print_t *print, const mp_raw_code_t *rc, size_t #else qstr source_file = cm->source_file; #endif - mp_printf(print, "File %s, code block '%s' (descriptor: %p, bytecode @%p %u bytes)\n", - qstr_str(source_file), qstr_str(block_name), rc, ip_start, (unsigned)fun_data_len); + mp_printf(print, "File %q, code block '%q' (descriptor: %p, bytecode @%p %u bytes)\n", + source_file, block_name, rc, ip_start, (unsigned)fun_data_len); // raw bytecode dump size_t prelude_size = ip - ip_start + n_info + n_cell; @@ -121,7 +121,7 @@ void mp_bytecode_print(const mp_print_t *print, const mp_raw_code_t *rc, size_t #if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE qst = cm->qstr_table[qst]; #endif - mp_printf(print, " %s", qstr_str(qst)); + mp_printf(print, " %q", qst); } mp_printf(print, "\n"); @@ -189,7 +189,7 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_LOAD_CONST_STRING: DECODE_QSTR; - mp_printf(print, "LOAD_CONST_STRING '%s'", qstr_str(qst)); + mp_printf(print, "LOAD_CONST_STRING '%q'", qst); break; case MP_BC_LOAD_CONST_OBJ: @@ -214,27 +214,27 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_LOAD_NAME: DECODE_QSTR; - mp_printf(print, "LOAD_NAME %s", qstr_str(qst)); + mp_printf(print, "LOAD_NAME %q", qst); break; case MP_BC_LOAD_GLOBAL: DECODE_QSTR; - mp_printf(print, "LOAD_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "LOAD_GLOBAL %q", qst); break; case MP_BC_LOAD_ATTR: DECODE_QSTR; - mp_printf(print, "LOAD_ATTR %s", qstr_str(qst)); + mp_printf(print, "LOAD_ATTR %q", qst); break; case MP_BC_LOAD_METHOD: DECODE_QSTR; - mp_printf(print, "LOAD_METHOD %s", qstr_str(qst)); + mp_printf(print, "LOAD_METHOD %q", qst); break; case MP_BC_LOAD_SUPER_METHOD: DECODE_QSTR; - mp_printf(print, "LOAD_SUPER_METHOD %s", qstr_str(qst)); + mp_printf(print, "LOAD_SUPER_METHOD %q", qst); break; case MP_BC_LOAD_BUILD_CLASS: @@ -257,17 +257,17 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_STORE_NAME: DECODE_QSTR; - mp_printf(print, "STORE_NAME %s", qstr_str(qst)); + mp_printf(print, "STORE_NAME %q", qst); break; case MP_BC_STORE_GLOBAL: DECODE_QSTR; - mp_printf(print, "STORE_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "STORE_GLOBAL %q", qst); break; case MP_BC_STORE_ATTR: DECODE_QSTR; - mp_printf(print, "STORE_ATTR %s", qstr_str(qst)); + mp_printf(print, "STORE_ATTR %q", qst); break; case MP_BC_STORE_SUBSCR: @@ -286,12 +286,12 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_DELETE_NAME: DECODE_QSTR; - mp_printf(print, "DELETE_NAME %s", qstr_str(qst)); + mp_printf(print, "DELETE_NAME %q", qst); break; case MP_BC_DELETE_GLOBAL: DECODE_QSTR; - mp_printf(print, "DELETE_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "DELETE_GLOBAL %q", qst); break; case MP_BC_DUP_TOP: @@ -506,12 +506,12 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, case MP_BC_IMPORT_NAME: DECODE_QSTR; - mp_printf(print, "IMPORT_NAME '%s'", qstr_str(qst)); + mp_printf(print, "IMPORT_NAME '%q'", qst); break; case MP_BC_IMPORT_FROM: DECODE_QSTR; - mp_printf(print, "IMPORT_FROM '%s'", qstr_str(qst)); + mp_printf(print, "IMPORT_FROM '%q'", qst); break; case MP_BC_IMPORT_STAR: @@ -527,10 +527,10 @@ const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip_start, mp_printf(print, "STORE_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI); } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_UNARY_OP_MULTI; - mp_printf(print, "UNARY_OP " UINT_FMT " %s", op, qstr_str(mp_unary_op_method_name[op])); + mp_printf(print, "UNARY_OP " UINT_FMT " %q", op, (qstr)mp_unary_op_method_name[op]); } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_BINARY_OP_MULTI; - mp_printf(print, "BINARY_OP " UINT_FMT " %s", op, qstr_str(mp_binary_op_method_name[op])); + mp_printf(print, "BINARY_OP " UINT_FMT " %q", op, (qstr)mp_binary_op_method_name[op]); } else { mp_printf(print, "code %p, byte code 0x%02x not implemented\n", ip - 1, ip[-1]); assert(0); diff --git a/py/stream.c b/py/stream.c index b14a34d2658..d3b0a0d80e2 100644 --- a/py/stream.c +++ b/py/stream.c @@ -460,13 +460,13 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream___exit___obj, 4, 4, mp_stream___ex static mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args) { // TODO: Could be uint64 mp_off_t offset = mp_obj_get_int(args[1]); - int whence = SEEK_SET; + int whence = MP_SEEK_SET; if (n_args == 3) { whence = mp_obj_get_int(args[2]); } // In POSIX, it's error to seek before end of stream, we enforce it here. - if (whence == SEEK_SET && offset < 0) { + if (whence == MP_SEEK_SET && offset < 0) { mp_raise_OSError(MP_EINVAL); } @@ -483,7 +483,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_seek_obj, 2, 3, stream_seek); static mp_obj_t stream_tell(mp_obj_t self) { mp_obj_t offset = MP_OBJ_NEW_SMALL_INT(0); - mp_obj_t whence = MP_OBJ_NEW_SMALL_INT(SEEK_CUR); + mp_obj_t whence = MP_OBJ_NEW_SMALL_INT(MP_SEEK_CUR); const mp_obj_t args[3] = {self, offset, whence}; return stream_seek(3, args); } diff --git a/py/unicode.c b/py/unicode.c index 81a37880f3c..4f6a273fe74 100644 --- a/py/unicode.c +++ b/py/unicode.c @@ -180,7 +180,7 @@ mp_uint_t unichar_xdigit_value(unichar c) { #if MICROPY_PY_BUILTINS_STR_UNICODE -bool utf8_check(const byte *p, size_t len) { +bool unicode_encoding_check(mp_encoding_t encoding, const byte *p, size_t len) { uint8_t need = 0; const byte *end = p + len; for (; p < end; p++) { @@ -193,7 +193,7 @@ bool utf8_check(const byte *p, size_t len) { return 0; } } else { - if (c >= 0xc0) { + if (encoding == MP_ENCODING_UTF8 && c >= 0xc0) { if (c >= 0xf8) { // mismatch return 0; diff --git a/py/unicode.h b/py/unicode.h index c1fb517894f..7b82c466e51 100644 --- a/py/unicode.h +++ b/py/unicode.h @@ -29,7 +29,14 @@ #include "py/mpconfig.h" #include "py/misc.h" +typedef enum { + MP_ENCODING_UTF8, + MP_ENCODING_ASCII, +} mp_encoding_t; + mp_uint_t utf8_ptr_to_index(const byte *s, const byte *ptr); -bool utf8_check(const byte *p, size_t len); + +// Returns true if bytes in buffer 'p' are valid according to encoding. +bool unicode_encoding_check(mp_encoding_t encoding, const byte *p, size_t len); #endif // MICROPY_INCLUDED_PY_UNICODE_H diff --git a/py/usermod.cmake b/py/usermod.cmake index 4a8b99ff31b..648f315b9d5 100644 --- a/py/usermod.cmake +++ b/py/usermod.cmake @@ -39,6 +39,13 @@ function(usermod_gather_sources SOURCES_VARNAME INCLUDE_DIRECTORIES_VARNAME INCL endif() endfunction() +# Extract c_module() entries from MICROPY_FROZEN_MANIFEST and merge them into +# USER_C_MODULES, so the loop below picks them up alongside USER_C_MODULES paths +# passed on the cmake command line. The port is responsible for resolving +# MICROPY_FROZEN_MANIFEST (including any board-config defaults) before +# including this file. +include(${MICROPY_DIR}/py/manifest.cmake) + # Include CMake files for user modules. if (USER_C_MODULES) foreach(USER_C_MODULE_PATH ${USER_C_MODULES}) diff --git a/py/version.py b/py/version.py index b9e889b86b6..7ef2a587628 100755 --- a/py/version.py +++ b/py/version.py @@ -23,7 +23,11 @@ def get_version_info_from_git(repo_path, extra_args=[]): *extra_args, ], cwd=repo_path, - stderr=subprocess.STDOUT, + # CIRCUITPY-CHANGE: Keep stderr out of the tag: e.g. with + # core.fsmonitor=true the daemon can emit a transient IPC + # error to stderr while the command still succeeds, and it + # must not end up inside MICROPY_GIT_TAG. + stderr=subprocess.DEVNULL, universal_newlines=True, ).strip() # CIRCUITPY-CHANGE @@ -39,7 +43,8 @@ def get_version_info_from_git(repo_path, extra_args=[]): git_hash = subprocess.check_output( ["git", "rev-parse", "--short", "HEAD"], cwd=repo_path, - stderr=subprocess.STDOUT, + # CIRCUITPY-CHANGE: same as above; stderr must not become the hash. + stderr=subprocess.DEVNULL, universal_newlines=True, ).strip() except subprocess.CalledProcessError: diff --git a/pyproject.toml b/pyproject.toml index e02375fd616..fcb6270b2dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,7 @@ [tool.ruff] target-version = "py312" line-length = 99 +builtins = [ "const", "execfile", "micropython", "ptr", "ptr8", "ptr16", "ptr32", "uint" ] # Include Python source files that don't end with .py extend-include = [ "tools/cc1" ] # Exclude third-party code from linting and formatting @@ -12,7 +13,6 @@ extend-exclude = [ "lib", "ports/analog/msdk", "ports/atmel-samd/asf4", - "ports/broadcom/peripherals", "ports/espress/tools", "ports/espressif/esp-idf", "ports/espressif/esp-protocols", @@ -43,7 +43,19 @@ format.exclude = [ "tests/micropython/test_normalize_newlines.py", "tests/micropython/viper_args.py", ] -lint.extend-select = [ "C9", "PLC" ] +lint.extend-select = [ "C9", "PLC", "W292" ] +lint.ignore = [ + "E401", + "E402", + "E722", + "E731", + "E741", + "F401", + "F403", + "F405", + "PLC0206", + "PLC0415", # conditional imports are common in MicroPython +] lint.exclude = [ # Ruff finds Python SyntaxError in these files "tests/basics/string_module_tstring.py", @@ -61,6 +73,7 @@ lint.exclude = [ "tests/cmdline/repl_autoindent.py", "tests/cmdline/repl_basic.py", "tests/cmdline/repl_cont.py", + "tests/cmdline/repl_ctrl_c_interrupt_execution.py", "tests/cmdline/repl_emacs_keys.py", "tests/cmdline/repl_paste.py", "tests/cmdline/repl_words_move.py", @@ -70,18 +83,6 @@ lint.exclude = [ "tests/micropython/heapalloc_fail_tstring.py", "tests/micropython/viper_args.py", ] -lint.extend-ignore = [ - "E401", - "E402", - "E722", - "E731", - "E741", - "F401", - "F403", - "F405", - "PLC0206", - "PLC0415", # conditional imports are common in MicroPython -] # manifest.py files are evaluated with some global names pre-defined lint.per-file-ignores."**/manifest.py" = [ "F821" ] lint.per-file-ignores."ports/**/boards/**/manifest_*.py" = [ "F821" ] @@ -100,6 +101,6 @@ ignore-words-list = "ans,aranges,asend,deques,dout,emac,extint,hsi,iput,mis,noti quiet-level = 3 skip = """\ */build*,./.git,./drivers/cc3100,./lib,./ports/cc3200/FreeRTOS,./ports/cc3200/bootmgr/sl,./ports/cc3200/hal,./ports/c\ - c3200/simplelink,./ports/cc3200/telnet,./ports/esp32/managed_components,./ports/nrf/drivers/bluetooth/s1*,./ports/stm\ - 32/usbhost,./tests,ACKNOWLEDGEMENTS,\ + c3200/simplelink,./ports/cc3200/telnet,./ports/esp32/managed_components,./ports/nrf/drivers/bluetooth/s1*,./ports/pso\ + c-edge/boards/*/bsp-cfg,./ports/stm32/usbhost,./tests,ACKNOWLEDGEMENTS,\ """ diff --git a/requirements-dev.txt b/requirements-dev.txt index a6fedc4b4af..db61d51ea90 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -46,4 +46,8 @@ setuptools tomlkit pytest pytest-rerunfailures +pytest-xdist perfetto + +# For littlefs flash image tests on the zephyr port native_simulators +littlefs-python diff --git a/shared-bindings/_bleio/Attribute.h b/shared-bindings/_bleio/Attribute.h index 51278dc0157..60a941a5772 100644 --- a/shared-bindings/_bleio/Attribute.h +++ b/shared-bindings/_bleio/Attribute.h @@ -14,3 +14,7 @@ extern const mp_obj_type_t bleio_attribute_type; extern void common_hal_bleio_attribute_security_mode_check_valid(bleio_attribute_security_mode_t security_mode); + +// True for the security modes that demand a man-in-the-middle-protected (authenticated) +// link: pairing must use numeric comparison or passkey entry, not "Just Works". +extern bool bleio_attribute_security_mode_requires_mitm(bleio_attribute_security_mode_t security_mode); diff --git a/shared-bindings/_bleio/CharacteristicBuffer.c b/shared-bindings/_bleio/CharacteristicBuffer.c index 07394256933..c0561f7f7eb 100644 --- a/shared-bindings/_bleio/CharacteristicBuffer.c +++ b/shared-bindings/_bleio/CharacteristicBuffer.c @@ -56,7 +56,7 @@ static mp_obj_t bleio_characteristic_buffer_make_new(const mp_obj_type_t *type, const mp_int_t buffer_size = mp_arg_validate_int_min(args[ARG_buffer_size].u_int, 1, MP_QSTR_buffer_size); bleio_characteristic_buffer_obj_t *self = - mp_obj_malloc(bleio_characteristic_buffer_obj_t, &bleio_characteristic_buffer_type); + mp_obj_malloc_with_finaliser(bleio_characteristic_buffer_obj_t, &bleio_characteristic_buffer_type); common_hal_bleio_characteristic_buffer_construct(self, characteristic, timeout, buffer_size); @@ -184,6 +184,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(bleio_characteristic_buffer_deinit_obj, bleio_c static const mp_rom_map_elem_t bleio_characteristic_buffer_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&bleio_characteristic_buffer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&bleio_characteristic_buffer_deinit_obj) }, // Standard stream methods. { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, diff --git a/shared-bindings/_bleio/Connection.c b/shared-bindings/_bleio/Connection.c index 4a81d753433..275fa0d435d 100644 --- a/shared-bindings/_bleio/Connection.c +++ b/shared-bindings/_bleio/Connection.c @@ -165,6 +165,78 @@ MP_PROPERTY_GETTER(bleio_connection_paired_obj, (mp_obj_t)&bleio_connection_get_paired_obj); +//| authenticated: bool +//| """True if the link is encrypted *and* authenticated against +//| man-in-the-middle attacks - i.e. LE Secure Connections numeric comparison (or +//| passkey entry) completed. A plain encrypted-but-unauthenticated ("Just Works") +//| link reads ``False``. +//| +//| Ports that don't implement authenticated pairing always report ``False``, +//| even for a bonded link.""" +static mp_obj_t bleio_connection_get_authenticated(mp_obj_t self_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return mp_obj_new_bool(common_hal_bleio_connection_get_authenticated(self)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(bleio_connection_get_authenticated_obj, bleio_connection_get_authenticated); + +MP_PROPERTY_GETTER(bleio_connection_authenticated_obj, + (mp_obj_t)&bleio_connection_get_authenticated_obj); + + +//| numeric_comparison: Optional[int] +//| """When the peer has started LE Secure Connections pairing and is waiting for the +//| user to confirm a 6-digit numeric-comparison value, this is that value (an ``int``, +//| 0-999999); otherwise ``None``. Poll it, show the value to the user, then answer +//| with `confirm_pairing`. This is the peripheral side of pairing: the peer drives +//| the procedure, so unlike `pair` this never blocks. +//| +//| Numeric comparison happens automatically when a central pairs to reach a +//| characteristic whose ``read_perm`` / ``write_perm`` is +//| `Attribute.LESC_ENCRYPT_WITH_MITM`. A peripheral with a display and a button +//| services it roughly like this:: +//| +//| while connection.connected: +//| code = connection.numeric_comparison +//| if code is not None: +//| display.show(f"Pair? {code:06d}") +//| connection.confirm_pairing(accept=button_a.value) +//| if connection.authenticated: +//| break +//| time.sleep(0.1) +//| +//| Only implemented on the espressif port.""" +static mp_obj_t bleio_connection_get_numeric_comparison(mp_obj_t self_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return common_hal_bleio_connection_get_numeric_comparison(self); +} +static MP_DEFINE_CONST_FUN_OBJ_1(bleio_connection_get_numeric_comparison_obj, bleio_connection_get_numeric_comparison); + +MP_PROPERTY_GETTER(bleio_connection_numeric_comparison_obj, + (mp_obj_t)&bleio_connection_get_numeric_comparison_obj); + + +//| def confirm_pairing(self, accept: bool) -> None: +//| """Answer a pending numeric-comparison pairing request (see +//| `numeric_comparison`). Pass ``accept=True`` if the value shown on the +//| peer matches the one shown here, ``accept=False`` to reject and abort pairing. +//| +//| :raises ConnectionError: if the connection has dropped. +//| :raises _bleio.BluetoothError: if no numeric-comparison request is pending - +//| nothing was offered on `numeric_comparison`, or it already expired +//| because the peer disconnected or the pairing procedure timed out.""" +//| ... +//| +static mp_obj_t bleio_connection_confirm_pairing(mp_obj_t self_in, mp_obj_t accept_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + common_hal_bleio_connection_confirm_pairing(self, mp_obj_is_true(accept_in)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(bleio_connection_confirm_pairing_obj, bleio_connection_confirm_pairing); + + //| connection_interval: float //| """Time between transmissions in milliseconds. Will be multiple of 1.25ms. Lower numbers //| increase speed and decrease latency but increase power consumption. @@ -223,12 +295,15 @@ MP_PROPERTY_GETTER(bleio_connection_max_packet_length_obj, static const mp_rom_map_elem_t bleio_connection_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_pair), MP_ROM_PTR(&bleio_connection_pair_obj) }, + { MP_ROM_QSTR(MP_QSTR_confirm_pairing), MP_ROM_PTR(&bleio_connection_confirm_pairing_obj) }, { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&bleio_connection_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_discover_remote_services), MP_ROM_PTR(&bleio_connection_discover_remote_services_obj) }, // Properties { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&bleio_connection_connected_obj) }, { MP_ROM_QSTR(MP_QSTR_paired), MP_ROM_PTR(&bleio_connection_paired_obj) }, + { MP_ROM_QSTR(MP_QSTR_authenticated), MP_ROM_PTR(&bleio_connection_authenticated_obj) }, + { MP_ROM_QSTR(MP_QSTR_numeric_comparison), MP_ROM_PTR(&bleio_connection_numeric_comparison_obj) }, { MP_ROM_QSTR(MP_QSTR_connection_interval), MP_ROM_PTR(&bleio_connection_connection_interval_obj) }, { MP_ROM_QSTR(MP_QSTR_max_packet_length), MP_ROM_PTR(&bleio_connection_max_packet_length_obj) }, }; diff --git a/shared-bindings/_bleio/Connection.h b/shared-bindings/_bleio/Connection.h index 4be4992fb2f..fbc856c7d45 100644 --- a/shared-bindings/_bleio/Connection.h +++ b/shared-bindings/_bleio/Connection.h @@ -18,6 +18,9 @@ void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self); bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self); mp_int_t common_hal_bleio_connection_get_max_packet_length(bleio_connection_internal_t *self); bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self); +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self); +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self); +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept); mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist); mp_float_t common_hal_bleio_connection_get_connection_interval(bleio_connection_internal_t *self); diff --git a/shared-bindings/_bleio/PacketBuffer.c b/shared-bindings/_bleio/PacketBuffer.c index aa1e6e8645a..8c633d50ef7 100644 --- a/shared-bindings/_bleio/PacketBuffer.c +++ b/shared-bindings/_bleio/PacketBuffer.c @@ -63,7 +63,7 @@ static mp_obj_t bleio_packet_buffer_make_new(const mp_obj_type_t *type, size_t n (size_t)mp_arg_validate_int_min(max_packet_size_int, 1, MP_QSTR_max_packet_size); } - bleio_packet_buffer_obj_t *self = mp_obj_malloc(bleio_packet_buffer_obj_t, &bleio_packet_buffer_type); + bleio_packet_buffer_obj_t *self = mp_obj_malloc_with_finaliser(bleio_packet_buffer_obj_t, &bleio_packet_buffer_type); common_hal_bleio_packet_buffer_construct(self, characteristic, buffer_size, max_packet_size); @@ -199,6 +199,7 @@ MP_PROPERTY_GETTER(bleio_packet_buffer_outgoing_packet_length_obj, static const mp_rom_map_elem_t bleio_packet_buffer_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&bleio_packet_buffer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&bleio_packet_buffer_deinit_obj) }, // Standard stream methods. { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&bleio_packet_buffer_readinto_obj) }, diff --git a/shared-bindings/aesio/aes.c b/shared-bindings/aesio/aes.c index 75f2850a7ef..eeb685a6652 100644 --- a/shared-bindings/aesio/aes.c +++ b/shared-bindings/aesio/aes.c @@ -98,9 +98,13 @@ static mp_obj_t aesio_aes_make_new(const mp_obj_type_t *type, size_t n_args, iv = bufinfo.buf; } + int counter = 0; + if (args[ARG_counter].u_obj != MP_OBJ_NULL) { + counter = mp_obj_get_int_truncated(args[ARG_counter].u_obj); + } + aesio_aes_obj_t *self = mp_obj_malloc(aesio_aes_obj_t, &aesio_aes_type); - common_hal_aesio_aes_construct(self, key, key_length, iv, mode, - args[ARG_counter].u_int); + common_hal_aesio_aes_construct(self, key, key_length, iv, mode, counter); return MP_OBJ_FROM_PTR(self); } diff --git a/shared-bindings/alarm/time/TimeAlarm.c b/shared-bindings/alarm/time/TimeAlarm.c index 1f1e214bd4b..6b8b25bc303 100644 --- a/shared-bindings/alarm/time/TimeAlarm.c +++ b/shared-bindings/alarm/time/TimeAlarm.c @@ -67,7 +67,7 @@ static mp_obj_t alarm_time_timealarm_make_new(const mp_obj_type_t *type, monotonic_time = mp_obj_get_float(args[ARG_monotonic_time].u_obj); } - mp_float_t monotonic_time_now = common_hal_time_monotonic_ms() / 1000.0; + mp_float_t monotonic_time_now = uint64_to_float(common_hal_time_monotonic_ms()) / MICROPY_FLOAT_CONST(1000.0); if (have_epoch) { #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE diff --git a/shared-bindings/analogbufio/BufferedIn.c b/shared-bindings/analogbufio/BufferedIn.c index 2c42ee1f325..398823809b0 100644 --- a/shared-bindings/analogbufio/BufferedIn.c +++ b/shared-bindings/analogbufio/BufferedIn.c @@ -126,7 +126,7 @@ static mp_obj_t analogbufio_bufferedin_obj_readinto(size_t n_args, const mp_obj_ mp_obj_t buffer = args[ARG_buffer].u_obj; // Buffer defined and allocated by user mp_buffer_info_t bufinfo; - mp_get_buffer_raise(buffer, &bufinfo, MP_BUFFER_READ); + mp_get_buffer_raise(buffer, &bufinfo, MP_BUFFER_WRITE); uint8_t bytes_per_sample = 1; if (bufinfo.typecode == 'H') { bytes_per_sample = 2; diff --git a/shared-bindings/analogio/AnalogIn.c b/shared-bindings/analogio/AnalogIn.c index b68b044ab52..9ea75fd7004 100644 --- a/shared-bindings/analogio/AnalogIn.c +++ b/shared-bindings/analogio/AnalogIn.c @@ -54,7 +54,7 @@ static mp_obj_t analogio_analogin_make_new(const mp_obj_type_t *type, // 1st argument is the pin const mcu_pin_obj_t *pin = common_hal_analogio_analogin_validate_pin(args[0]); - analogio_analogin_obj_t *self = mp_obj_malloc(analogio_analogin_obj_t, &analogio_analogin_type); + analogio_analogin_obj_t *self = mp_obj_malloc_with_finaliser(analogio_analogin_obj_t, &analogio_analogin_type); common_hal_analogio_analogin_construct(self, pin); return MP_OBJ_FROM_PTR(self); @@ -129,6 +129,7 @@ MP_PROPERTY_GETTER(analogio_analogin_reference_voltage_obj, static const mp_rom_map_elem_t analogio_analogin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&analogio_analogin_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&analogio_analogin_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&analogio_analogin_value_obj)}, diff --git a/shared-bindings/analogio/AnalogOut.c b/shared-bindings/analogio/AnalogOut.c index b9e5824f978..24f6fb87d3a 100644 --- a/shared-bindings/analogio/AnalogOut.c +++ b/shared-bindings/analogio/AnalogOut.c @@ -46,7 +46,7 @@ static mp_obj_t analogio_analogout_make_new(const mp_obj_type_t *type, mp_uint_t const mcu_pin_obj_t *pin = validate_obj_is_free_pin(args[0], MP_QSTR_pin); - analogio_analogout_obj_t *self = mp_obj_malloc(analogio_analogout_obj_t, &analogio_analogout_type); + analogio_analogout_obj_t *self = mp_obj_malloc_with_finaliser(analogio_analogout_obj_t, &analogio_analogout_type); common_hal_analogio_analogout_construct(self, pin); return MP_OBJ_FROM_PTR(self); @@ -104,6 +104,7 @@ MP_PROPERTY_GETSET(analogio_analogout_value_obj, static const mp_rom_map_elem_t analogio_analogout_locals_dict_table[] = { // instance methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&analogio_analogout_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&analogio_analogout_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/audiobusio/I2SOut.c b/shared-bindings/audiobusio/I2SOut.c index a104b20b9da..7769352a1da 100644 --- a/shared-bindings/audiobusio/I2SOut.c +++ b/shared-bindings/audiobusio/I2SOut.c @@ -98,7 +98,7 @@ static mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_a { MP_QSTR_word_select, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_data, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_main_clock, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none} }, - { MP_QSTR_left_justified, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_bool = false} }, + { MP_QSTR_left_justified, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, { MP_QSTR_external_clock, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; diff --git a/shared-bindings/audiobusio/PDMIn.c b/shared-bindings/audiobusio/PDMIn.c index aba8a019f35..79fb6bff1e4 100644 --- a/shared-bindings/audiobusio/PDMIn.c +++ b/shared-bindings/audiobusio/PDMIn.c @@ -11,6 +11,7 @@ #include "py/mphal.h" #include "py/objproperty.h" #include "py/runtime.h" +#include "extmod/vfs.h" #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/audiobusio/PDMIn.h" #include "shared-bindings/util.h" @@ -96,15 +97,17 @@ static mp_obj_t audiobusio_pdmin_make_new(const mp_obj_type_t *type, size_t n_ar const mcu_pin_obj_t *clock_pin = validate_obj_is_free_pin(args[ARG_clock_pin].u_obj, MP_QSTR_clock_pin); const mcu_pin_obj_t *data_pin = validate_obj_is_free_pin(args[ARG_data_pin].u_obj, MP_QSTR_data_pin); - uint32_t sample_rate = args[ARG_sample_rate].u_int; - uint8_t bit_depth = args[ARG_bit_depth].u_int; - if (bit_depth % 8 != 0) { + uint32_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate); + mp_int_t requested_bit_depth = args[ARG_bit_depth].u_int; + if (requested_bit_depth != 8 && requested_bit_depth != 16) { mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be multiple of 8."), MP_QSTR_bit_depth); } - uint8_t oversample = args[ARG_oversample].u_int; - if (oversample % 8 != 0) { + uint8_t bit_depth = requested_bit_depth; + mp_int_t requested_oversample = args[ARG_oversample].u_int; + if (requested_oversample < 8 || requested_oversample > 255 || requested_oversample % 8 != 0) { mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be multiple of 8."), MP_QSTR_oversample); } + uint8_t oversample = requested_oversample; bool mono = args[ARG_mono].u_bool; mp_float_t startup_delay = (args[ARG_startup_delay].u_obj == MP_OBJ_NULL) @@ -172,7 +175,7 @@ static mp_obj_t audiobusio_pdmin_obj_record(mp_obj_t self_obj, mp_obj_t destinat mp_arg_validate_length_min(length, 0, MP_QSTR_length); mp_buffer_info_t bufinfo; - if (mp_obj_is_type(destination, &mp_type_fileio)) { + if (mp_obj_is_fileio(destination)) { mp_raise_NotImplementedError(MP_ERROR_TEXT("Cannot record to a file")); } diff --git a/shared-bindings/audiocore/RawSample.c b/shared-bindings/audiocore/RawSample.c index 8dc57903286..b0a3dc8e46d 100644 --- a/shared-bindings/audiocore/RawSample.c +++ b/shared-bindings/audiocore/RawSample.c @@ -103,7 +103,7 @@ static mp_obj_t audioio_rawsample_make_new(const mp_obj_type_t *type, size_t n_a mp_raise_ValueError_varg(MP_ERROR_TEXT("Length of %q must be an even multiple of channel_count * type_size"), MP_QSTR_buffer); } - audioio_rawsample_obj_t *self = mp_obj_malloc(audioio_rawsample_obj_t, &audioio_rawsample_type); + audioio_rawsample_obj_t *self = mp_obj_malloc_with_finaliser(audioio_rawsample_obj_t, &audioio_rawsample_type); common_hal_audioio_rawsample_construct(self, ((uint8_t *)bufinfo.buf), bufinfo.len, @@ -151,6 +151,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(audioio_rawsample_deinit_obj, audioio_rawsample static const mp_rom_map_elem_t audioio_rawsample_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_rawsample_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audioio_rawsample_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/audiocore/WaveFile.c b/shared-bindings/audiocore/WaveFile.c index d0aa011313c..cba1a9adbfc 100644 --- a/shared-bindings/audiocore/WaveFile.c +++ b/shared-bindings/audiocore/WaveFile.c @@ -12,7 +12,7 @@ #include "shared-bindings/audiocore/WaveFile.h" #include "shared-bindings/audiocore/__init__.h" #include "shared-bindings/util.h" -#include "extmod/vfs_posix.h" +#include "extmod/vfs.h" //| class WaveFile: //| """Load a wave file for audio playback @@ -61,9 +61,6 @@ static mp_obj_t audioio_wavefile_make_new(const mp_obj_type_t *type, size_t n_ar arg = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), arg, MP_ROM_QSTR(MP_QSTR_rb)); } - if (!mp_obj_is_type(arg, &mp_type_vfs_fat_fileio)) { - mp_raise_TypeError(MP_ERROR_TEXT("file must be a file opened in byte mode")); - } uint8_t *buffer = NULL; size_t buffer_size = 0; if (n_args >= 2) { @@ -76,8 +73,8 @@ static mp_obj_t audioio_wavefile_make_new(const mp_obj_type_t *type, size_t n_ar } } - audioio_wavefile_obj_t *self = mp_obj_malloc(audioio_wavefile_obj_t, &audioio_wavefile_type); - common_hal_audioio_wavefile_construct(self, MP_OBJ_TO_PTR(arg), + audioio_wavefile_obj_t *self = mp_obj_malloc_with_finaliser(audioio_wavefile_obj_t, &audioio_wavefile_type); + common_hal_audioio_wavefile_construct(self, arg, buffer, buffer_size); return MP_OBJ_FROM_PTR(self); @@ -123,6 +120,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(audioio_wavefile_deinit_obj, audioio_wavefile_d static const mp_rom_map_elem_t audioio_wavefile_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_wavefile_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audioio_wavefile_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/audiocore/WaveFile.h b/shared-bindings/audiocore/WaveFile.h index 5249959a1cc..4a1b6e1436e 100644 --- a/shared-bindings/audiocore/WaveFile.h +++ b/shared-bindings/audiocore/WaveFile.h @@ -7,13 +7,12 @@ #pragma once #include "py/obj.h" -#include "extmod/vfs_fat.h" #include "shared-module/audiocore/WaveFile.h" extern const mp_obj_type_t audioio_wavefile_type; void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t *self, - pyb_file_obj_t *file, uint8_t *buffer, size_t buffer_size); + mp_obj_t file, uint8_t *buffer, size_t buffer_size); void common_hal_audioio_wavefile_deinit(audioio_wavefile_obj_t *self); diff --git a/shared-bindings/audiocore/__init__.c b/shared-bindings/audiocore/__init__.c index b2b6c4da0f3..2dadc8877f9 100644 --- a/shared-bindings/audiocore/__init__.c +++ b/shared-bindings/audiocore/__init__.c @@ -145,7 +145,8 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiosample_get_sample_rate_obj, audiosample_obj_get_s static mp_obj_t audiosample_obj_set_sample_rate(mp_obj_t self_in, mp_obj_t sample_rate) { audiosample_base_t *self = MP_OBJ_TO_PTR(self_in); audiosample_check_for_deinit(self); - audiosample_set_sample_rate(audiosample_check(self_in), mp_obj_get_int(sample_rate)); + audiosample_set_sample_rate(audiosample_check(self_in), + (uint32_t)mp_arg_validate_int_min(mp_obj_get_int(sample_rate), 1, MP_QSTR_sample_rate)); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_2(audiosample_set_sample_rate_obj, audiosample_obj_set_sample_rate); diff --git a/shared-bindings/audiodelays/Chorus.c b/shared-bindings/audiodelays/Chorus.c index 41bcbb7ef93..a08c85241d9 100644 --- a/shared-bindings/audiodelays/Chorus.c +++ b/shared-bindings/audiodelays/Chorus.c @@ -96,7 +96,7 @@ static mp_obj_t audiodelays_chorus_make_new(const mp_obj_type_t *type, size_t n_ mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - audiodelays_chorus_obj_t *self = mp_obj_malloc(audiodelays_chorus_obj_t, &audiodelays_chorus_type); + audiodelays_chorus_obj_t *self = mp_obj_malloc_with_finaliser(audiodelays_chorus_obj_t, &audiodelays_chorus_type); common_hal_audiodelays_chorus_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_voices].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); return MP_OBJ_FROM_PTR(self); @@ -252,6 +252,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_chorus_stop_obj, audiodelays_chorus_obj_st static const mp_rom_map_elem_t audiodelays_chorus_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_chorus_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiodelays_chorus_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiodelays_chorus___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_chorus_play_obj) }, diff --git a/shared-bindings/audiodelays/Echo.c b/shared-bindings/audiodelays/Echo.c index f1c79de0e13..aa8eaf75e23 100644 --- a/shared-bindings/audiodelays/Echo.c +++ b/shared-bindings/audiodelays/Echo.c @@ -105,7 +105,7 @@ static mp_obj_t audiodelays_echo_make_new(const mp_obj_type_t *type, size_t n_ar mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - audiodelays_echo_obj_t *self = mp_obj_malloc(audiodelays_echo_obj_t, &audiodelays_echo_type); + audiodelays_echo_obj_t *self = mp_obj_malloc_with_finaliser(audiodelays_echo_obj_t, &audiodelays_echo_type); common_hal_audiodelays_echo_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_decay].u_obj, args[ARG_filter].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate, args[ARG_freq_shift].u_bool); return MP_OBJ_FROM_PTR(self); @@ -302,6 +302,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_echo_stop_obj, audiodelays_echo_obj_stop); static const mp_rom_map_elem_t audiodelays_echo_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_echo_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiodelays_echo_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_echo_play_obj) }, diff --git a/shared-bindings/audiodelays/Flanger.c b/shared-bindings/audiodelays/Flanger.c index 7c16e164944..0290c5288e0 100644 --- a/shared-bindings/audiodelays/Flanger.c +++ b/shared-bindings/audiodelays/Flanger.c @@ -116,7 +116,7 @@ static mp_obj_t audiodelays_flanger_make_new(const mp_obj_type_t *type, size_t n mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - audiodelays_flanger_obj_t *self = mp_obj_malloc(audiodelays_flanger_obj_t, &audiodelays_flanger_type); + audiodelays_flanger_obj_t *self = mp_obj_malloc_with_finaliser(audiodelays_flanger_obj_t, &audiodelays_flanger_type); common_hal_audiodelays_flanger_construct(self, max_delay_ms, args[ARG_min_delay_ms].u_obj, args[ARG_rate].u_obj, args[ARG_depth].u_obj, args[ARG_feedback].u_obj, args[ARG_mix].u_obj, args[ARG_invert].u_bool, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); return MP_OBJ_FROM_PTR(self); @@ -355,6 +355,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_flanger_stop_obj, audiodelays_flanger_obj_ static const mp_rom_map_elem_t audiodelays_flanger_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_flanger_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiodelays_flanger_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiodelays_flanger___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_flanger_play_obj) }, diff --git a/shared-bindings/audiodelays/GranularPitchShift.c b/shared-bindings/audiodelays/GranularPitchShift.c index afbda30156a..9b1acfb6760 100644 --- a/shared-bindings/audiodelays/GranularPitchShift.c +++ b/shared-bindings/audiodelays/GranularPitchShift.c @@ -112,7 +112,7 @@ static mp_obj_t audiodelays_granular_pitch_shift_make_new(const mp_obj_type_t *t } audiodelays_granular_pitch_shift_obj_t *self = - mp_obj_malloc(audiodelays_granular_pitch_shift_obj_t, &audiodelays_granular_pitch_shift_type); + mp_obj_malloc_with_finaliser(audiodelays_granular_pitch_shift_obj_t, &audiodelays_granular_pitch_shift_type); common_hal_audiodelays_granular_pitch_shift_construct(self, args[ARG_semitones].u_obj, args[ARG_mix].u_obj, @@ -285,6 +285,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_granular_pitch_shift_stop_obj, audiodelays static const mp_rom_map_elem_t audiodelays_granular_pitch_shift_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_granular_pitch_shift_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiodelays_granular_pitch_shift_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiodelays_granular_pitch_shift___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_granular_pitch_shift_play_obj) }, diff --git a/shared-bindings/audiodelays/MultiTapDelay.c b/shared-bindings/audiodelays/MultiTapDelay.c index 2126dbf9904..afac5022540 100644 --- a/shared-bindings/audiodelays/MultiTapDelay.c +++ b/shared-bindings/audiodelays/MultiTapDelay.c @@ -104,7 +104,7 @@ static mp_obj_t audiodelays_multi_tap_delay_make_new(const mp_obj_type_t *type, mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - audiodelays_multi_tap_delay_obj_t *self = mp_obj_malloc(audiodelays_multi_tap_delay_obj_t, &audiodelays_multi_tap_delay_type); + audiodelays_multi_tap_delay_obj_t *self = mp_obj_malloc_with_finaliser(audiodelays_multi_tap_delay_obj_t, &audiodelays_multi_tap_delay_type); common_hal_audiodelays_multi_tap_delay_construct(self, max_delay_ms, args[ARG_delay_ms].u_obj, args[ARG_decay].u_obj, args[ARG_mix].u_obj, args[ARG_taps].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); return MP_OBJ_FROM_PTR(self); @@ -279,6 +279,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_multi_tap_delay_stop_obj, audiodelays_mult static const mp_rom_map_elem_t audiodelays_multi_tap_delay_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_multi_tap_delay_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiodelays_multi_tap_delay_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_multi_tap_delay_play_obj) }, diff --git a/shared-bindings/audiodelays/PitchShift.c b/shared-bindings/audiodelays/PitchShift.c index 49950df6a34..098fe7a7dc3 100644 --- a/shared-bindings/audiodelays/PitchShift.c +++ b/shared-bindings/audiodelays/PitchShift.c @@ -95,7 +95,7 @@ static mp_obj_t audiodelays_pitch_shift_make_new(const mp_obj_type_t *type, size } audiodelays_pitch_shift_obj_t *self = - mp_obj_malloc(audiodelays_pitch_shift_obj_t, &audiodelays_pitch_shift_type); + mp_obj_malloc_with_finaliser(audiodelays_pitch_shift_obj_t, &audiodelays_pitch_shift_type); common_hal_audiodelays_pitch_shift_construct(self, args[ARG_semitones].u_obj, args[ARG_mix].u_obj, @@ -268,6 +268,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiodelays_pitch_shift_stop_obj, audiodelays_pitch_sh static const mp_rom_map_elem_t audiodelays_pitch_shift_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiodelays_pitch_shift_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiodelays_pitch_shift_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiodelays_pitch_shift___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiodelays_pitch_shift_play_obj) }, diff --git a/shared-bindings/audiofilewriter/AudioFileWriter.c b/shared-bindings/audiofilewriter/AudioFileWriter.c index 8dbdba5d448..fa899318e98 100644 --- a/shared-bindings/audiofilewriter/AudioFileWriter.c +++ b/shared-bindings/audiofilewriter/AudioFileWriter.c @@ -81,7 +81,7 @@ static mp_obj_t audiofilewriter_audiofilewriter_make_new(const mp_obj_type_t *ty // A buffer smaller than one source buffer is useless; require a sane floor. mp_int_t buffer_size = mp_arg_validate_int_min(args[ARG_buffer_size].u_int, 512, MP_QSTR_buffer_size); - audiofilewriter_audiofilewriter_obj_t *self = mp_obj_malloc(audiofilewriter_audiofilewriter_obj_t, &audiofilewriter_audiofilewriter_type); + audiofilewriter_audiofilewriter_obj_t *self = mp_obj_malloc_with_finaliser(audiofilewriter_audiofilewriter_obj_t, &audiofilewriter_audiofilewriter_type); common_hal_audiofilewriter_audiofilewriter_construct(self, args[ARG_file].u_obj, (uint32_t)buffer_size); return MP_OBJ_FROM_PTR(self); @@ -164,6 +164,7 @@ MP_PROPERTY_GETTER(audiofilewriter_audiofilewriter_playing_obj, static const mp_rom_map_elem_t audiofilewriter_audiofilewriter_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiofilewriter_audiofilewriter_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiofilewriter_audiofilewriter_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiofilewriter_audiofilewriter_play_obj) }, diff --git a/shared-bindings/audiofilters/Distortion.c b/shared-bindings/audiofilters/Distortion.c index 97f3288acc7..8b15177906c 100644 --- a/shared-bindings/audiofilters/Distortion.c +++ b/shared-bindings/audiofilters/Distortion.c @@ -146,7 +146,7 @@ static mp_obj_t audiofilters_distortion_make_new(const mp_obj_type_t *type, size } audiofilters_distortion_obj_t *self = - mp_obj_malloc(audiofilters_distortion_obj_t, &audiofilters_distortion_type); + mp_obj_malloc_with_finaliser(audiofilters_distortion_obj_t, &audiofilters_distortion_type); common_hal_audiofilters_distortion_construct(self, args[ARG_drive].u_obj, args[ARG_pre_gain].u_obj, @@ -368,6 +368,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_distortion_stop_obj, audiofilters_distort static const mp_rom_map_elem_t audiofilters_distortion_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiofilters_distortion_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiofilters_distortion_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiofilters_distortion_play_obj) }, diff --git a/shared-bindings/audiofilters/Filter.c b/shared-bindings/audiofilters/Filter.c index 2d0d12b226a..c226b499bfd 100644 --- a/shared-bindings/audiofilters/Filter.c +++ b/shared-bindings/audiofilters/Filter.c @@ -90,7 +90,7 @@ static mp_obj_t audiofilters_filter_make_new(const mp_obj_type_t *type, size_t n mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - audiofilters_filter_obj_t *self = mp_obj_malloc(audiofilters_filter_obj_t, &audiofilters_filter_type); + audiofilters_filter_obj_t *self = mp_obj_malloc_with_finaliser(audiofilters_filter_obj_t, &audiofilters_filter_type); common_hal_audiofilters_filter_construct(self, args[ARG_filter].u_obj, args[ARG_mix].u_obj, @@ -232,6 +232,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_stop_obj, audiofilters_filter_obj_ static const mp_rom_map_elem_t audiofilters_filter_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiofilters_filter_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiofilters_filter_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiofilters_filter_play_obj) }, diff --git a/shared-bindings/audiofilters/Phaser.c b/shared-bindings/audiofilters/Phaser.c index 2aed9623b72..b9af2cbc78b 100644 --- a/shared-bindings/audiofilters/Phaser.c +++ b/shared-bindings/audiofilters/Phaser.c @@ -90,7 +90,7 @@ static mp_obj_t audiofilters_phaser_make_new(const mp_obj_type_t *type, size_t n mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } - audiofilters_phaser_obj_t *self = mp_obj_malloc(audiofilters_phaser_obj_t, &audiofilters_phaser_type); + audiofilters_phaser_obj_t *self = mp_obj_malloc_with_finaliser(audiofilters_phaser_obj_t, &audiofilters_phaser_type); common_hal_audiofilters_phaser_construct(self, args[ARG_frequency].u_obj, args[ARG_feedback].u_obj, @@ -269,6 +269,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_phaser_stop_obj, audiofilters_phaser_obj_ static const mp_rom_map_elem_t audiofilters_phaser_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiofilters_phaser_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiofilters_phaser_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiofilters_phaser_play_obj) }, diff --git a/shared-bindings/audiofreeverb/Freeverb.c b/shared-bindings/audiofreeverb/Freeverb.c index 83c9eb7c01f..4b04a3497df 100644 --- a/shared-bindings/audiofreeverb/Freeverb.c +++ b/shared-bindings/audiofreeverb/Freeverb.c @@ -101,7 +101,7 @@ static mp_obj_t audiofreeverb_freeverb_make_new(const mp_obj_type_t *type, size_ mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 16")); } - audiofreeverb_freeverb_obj_t *self = mp_obj_malloc(audiofreeverb_freeverb_obj_t, &audiofreeverb_freeverb_type); + audiofreeverb_freeverb_obj_t *self = mp_obj_malloc_with_finaliser(audiofreeverb_freeverb_obj_t, &audiofreeverb_freeverb_type); common_hal_audiofreeverb_freeverb_construct(self, args[ARG_roomsize].u_obj, args[ARG_damp].u_obj, args[ARG_pre_filter].u_obj, args[ARG_post_filter].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); return MP_OBJ_FROM_PTR(self); @@ -293,6 +293,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(audiofreeverb_freeverb_stop_obj, audiofreeverb_freever static const mp_rom_map_elem_t audiofreeverb_freeverb_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiofreeverb_freeverb_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiofreeverb_freeverb_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiofreeverb_freeverb_play_obj) }, diff --git a/shared-bindings/audioi2sin/I2SIn.c b/shared-bindings/audioi2sin/I2SIn.c index 22f1fb2aaad..0e08e351ad4 100644 --- a/shared-bindings/audioi2sin/I2SIn.c +++ b/shared-bindings/audioi2sin/I2SIn.c @@ -7,6 +7,7 @@ #include #include "extmod/vfs_fat.h" +#include "extmod/vfs.h" #include "shared/runtime/context_manager_helpers.h" #include "py/binary.h" #include "py/mphal.h" @@ -222,7 +223,7 @@ static mp_obj_t audioi2sin_i2sin_obj_record(mp_obj_t self_obj, mp_obj_t destinat mp_arg_validate_length_min(length, 0, MP_QSTR_length); mp_buffer_info_t bufinfo; - if (mp_obj_is_type(destination, &mp_type_fileio)) { + if (mp_obj_is_fileio(destination)) { mp_raise_NotImplementedError(MP_ERROR_TEXT("Cannot record to a file")); } mp_get_buffer_raise(destination, &bufinfo, MP_BUFFER_WRITE); diff --git a/shared-bindings/audiomixer/Mixer.c b/shared-bindings/audiomixer/Mixer.c index 516d079cd2a..ac87bb850a6 100644 --- a/shared-bindings/audiomixer/Mixer.c +++ b/shared-bindings/audiomixer/Mixer.c @@ -85,7 +85,7 @@ static mp_obj_t audiomixer_mixer_make_new(const mp_obj_type_t *type, size_t n_ar mp_raise_ValueError(MP_ERROR_TEXT("bits_per_sample must be 8 or 16")); } audiomixer_mixer_obj_t *self = - mp_obj_malloc_var(audiomixer_mixer_obj_t, voice, mp_obj_t, voice_count, &audiomixer_mixer_type); + mp_obj_malloc_var_with_finaliser(audiomixer_mixer_obj_t, voice, mp_obj_t, voice_count, &audiomixer_mixer_type); common_hal_audiomixer_mixer_construct(self, voice_count, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); for (int v = 0; v < voice_count; v++) { @@ -185,10 +185,8 @@ static mp_obj_t audiomixer_mixer_obj_play(size_t n_args, const mp_obj_t *pos_arg mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - uint8_t v = args[ARG_voice].u_int; - if (v > (self->voice_count - 1)) { - mp_arg_error_invalid(MP_QSTR_voice); - } + uint8_t v = mp_arg_validate_int_range(args[ARG_voice].u_int, 0, self->voice_count - 1, + MP_QSTR_voice); audiomixer_mixervoice_obj_t *voice = MP_OBJ_TO_PTR(self->voice[v]); mp_obj_t sample = args[ARG_sample].u_obj; common_hal_audiomixer_mixervoice_play(voice, sample, args[ARG_loop].u_bool); @@ -212,10 +210,8 @@ static mp_obj_t audiomixer_mixer_obj_stop_voice(size_t n_args, const mp_obj_t *p mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - uint8_t v = args[ARG_voice].u_int; - if (v > (self->voice_count - 1)) { - mp_arg_error_invalid(MP_QSTR_voice); - } + uint8_t v = mp_arg_validate_int_range(args[ARG_voice].u_int, 0, self->voice_count - 1, + MP_QSTR_voice); audiomixer_mixervoice_obj_t *voice = MP_OBJ_TO_PTR(self->voice[v]); common_hal_audiomixer_mixervoice_stop(voice); return mp_const_none; @@ -226,6 +222,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(audiomixer_mixer_stop_voice_obj, 1, audiomixer_mixer_ static const mp_rom_map_elem_t audiomixer_mixer_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiomixer_mixer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiomixer_mixer_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiomixer_mixer_play_obj) }, diff --git a/shared-bindings/audiospeed/Resampler.c b/shared-bindings/audiospeed/Resampler.c index 8abf52905d4..58e2a04944a 100644 --- a/shared-bindings/audiospeed/Resampler.c +++ b/shared-bindings/audiospeed/Resampler.c @@ -56,7 +56,7 @@ static mp_obj_t audiospeed_resampler_make_new(const mp_obj_type_t *type, mp_obj_t source = args[ARG_source].u_obj; audiosample_check(source); - audiospeed_resampler_obj_t *self = mp_obj_malloc(audiospeed_resampler_obj_t, &audiospeed_resampler_type); + audiospeed_resampler_obj_t *self = mp_obj_malloc_with_finaliser(audiospeed_resampler_obj_t, &audiospeed_resampler_type); common_hal_audiospeed_resampler_construct(self, source); return MP_OBJ_FROM_PTR(self); } @@ -88,6 +88,7 @@ MP_PROPERTY_GETTER(audiospeed_resampler_rate_obj, static const mp_rom_map_elem_t audiospeed_resampler_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiospeed_resampler_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiospeed_resampler_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/audiospeed/SpeedChanger.c b/shared-bindings/audiospeed/SpeedChanger.c index bcac6b7ff20..7cf8d06362c 100644 --- a/shared-bindings/audiospeed/SpeedChanger.c +++ b/shared-bindings/audiospeed/SpeedChanger.c @@ -60,7 +60,7 @@ static mp_obj_t audiospeed_speedchanger_make_new(const mp_obj_type_t *type, mp_obj_t source = args[ARG_source].u_obj; audiosample_check(source); - audiospeed_speedchanger_obj_t *self = mp_obj_malloc(audiospeed_speedchanger_obj_t, &audiospeed_speedchanger_type); + audiospeed_speedchanger_obj_t *self = mp_obj_malloc_with_finaliser(audiospeed_speedchanger_obj_t, &audiospeed_speedchanger_type); common_hal_audiospeed_speedchanger_construct(self, source, args[ARG_rate].u_obj); return MP_OBJ_FROM_PTR(self); } @@ -100,6 +100,7 @@ MP_PROPERTY_GETSET(audiospeed_speedchanger_rate_obj, static const mp_rom_map_elem_t audiospeed_speedchanger_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiospeed_speedchanger_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiospeed_speedchanger_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_rate), MP_ROM_PTR(&audiospeed_speedchanger_rate_obj) }, diff --git a/shared-bindings/aurora_epaper/aurora_framebuffer.c b/shared-bindings/aurora_epaper/aurora_framebuffer.c index 49e0b34a856..9595d7c6015 100644 --- a/shared-bindings/aurora_epaper/aurora_framebuffer.c +++ b/shared-bindings/aurora_epaper/aurora_framebuffer.c @@ -175,6 +175,7 @@ MP_PROPERTY_GETSET(aurora_epaper_framebuffer_free_bus_obj, static const mp_rom_map_elem_t aurora_epaper_framebuffer_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&aurora_epaper_framebuffer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&aurora_epaper_framebuffer_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_set_temperature), MP_ROM_PTR(&aurora_epaper_frambuffer_set_temperature_obj) }, { MP_ROM_QSTR(MP_QSTR_free_bus), MP_ROM_PTR(&aurora_epaper_framebuffer_free_bus_obj) }, }; diff --git a/shared-bindings/bitbangio/I2C.c b/shared-bindings/bitbangio/I2C.c index e1413392699..b3908cdde0a 100644 --- a/shared-bindings/bitbangio/I2C.c +++ b/shared-bindings/bitbangio/I2C.c @@ -57,8 +57,10 @@ static mp_obj_t bitbangio_i2c_make_new(const mp_obj_type_t *type, size_t n_args, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_int_t frequency = mp_arg_validate_int_min(args[ARG_frequency].u_int, 1, MP_QSTR_frequency); + bitbangio_i2c_obj_t *self = mp_obj_malloc_with_finaliser(bitbangio_i2c_obj_t, &bitbangio_i2c_type); - shared_module_bitbangio_i2c_construct(self, args[ARG_scl].u_obj, args[ARG_sda].u_obj, args[ARG_frequency].u_int, args[ARG_timeout].u_int); + shared_module_bitbangio_i2c_construct(self, args[ARG_scl].u_obj, args[ARG_sda].u_obj, frequency, args[ARG_timeout].u_int); return MP_OBJ_FROM_PTR(self); } @@ -352,6 +354,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(bitbangio_i2c_writeto_then_readfrom_obj, 1, bitbangio static const mp_rom_map_elem_t bitbangio_i2c_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&bitbangio_i2c_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&bitbangio_i2c_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_probe), MP_ROM_PTR(&bitbangio_i2c_probe_obj) }, diff --git a/shared-bindings/bitbangio/SPI.c b/shared-bindings/bitbangio/SPI.c index de021867f3c..21ad75be1d4 100644 --- a/shared-bindings/bitbangio/SPI.c +++ b/shared-bindings/bitbangio/SPI.c @@ -65,7 +65,7 @@ static mp_obj_t bitbangio_spi_make_new(const mp_obj_type_t *type, size_t n_args, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - bitbangio_spi_obj_t *self = mp_obj_malloc(bitbangio_spi_obj_t, &bitbangio_spi_type); + bitbangio_spi_obj_t *self = mp_obj_malloc_with_finaliser(bitbangio_spi_obj_t, &bitbangio_spi_type); shared_module_bitbangio_spi_construct(self, args[ARG_clock].u_obj, args[ARG_MOSI].u_obj, args[ARG_MISO].u_obj); return MP_OBJ_FROM_PTR(self); } @@ -133,12 +133,13 @@ static mp_obj_t bitbangio_spi_configure(size_t n_args, const mp_obj_t *pos_args, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint32_t baudrate = (uint32_t)mp_arg_validate_int_min(args[ARG_baudrate].u_int, 1, MP_QSTR_baudrate); uint8_t polarity = (uint8_t)mp_arg_validate_int_range(args[ARG_polarity].u_int, 0, 1, MP_QSTR_polarity); uint8_t phase = (uint8_t)mp_arg_validate_int_range(args[ARG_phase].u_int, 0, 1, MP_QSTR_phase); uint8_t bits = (uint8_t)mp_arg_validate_int_range(args[ARG_bits].u_int, 8, 9, MP_QSTR_bits); - shared_module_bitbangio_spi_configure(self, args[ARG_baudrate].u_int, polarity, phase, bits); + shared_module_bitbangio_spi_configure(self, baudrate, polarity, phase, bits); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_KW(bitbangio_spi_configure_obj, 1, bitbangio_spi_configure); @@ -381,6 +382,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(bitbangio_spi_write_readinto_obj, 1, bitbangio_spi_wr static const mp_rom_map_elem_t bitbangio_spi_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&bitbangio_spi_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&bitbangio_spi_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/bitmapfilter/__init__.c b/shared-bindings/bitmapfilter/__init__.c index 7c5344dde11..8d01ad482b2 100644 --- a/shared-bindings/bitmapfilter/__init__.c +++ b/shared-bindings/bitmapfilter/__init__.c @@ -137,7 +137,7 @@ static mp_obj_t bitmapfilter_morph(size_t n_args, const mp_obj_t *pos_args, mp_m mp_float_t m = get_m(args[ARG_mul].u_obj, weight_sum); shared_module_bitmapfilter_morph(bitmap, mask, sq_n_weights / 2, iweights, m, b, - args[ARG_threshold].u_bool, args[ARG_offset].u_bool, args[ARG_invert].u_bool); + args[ARG_threshold].u_bool, args[ARG_offset].u_int, args[ARG_invert].u_bool); return args[ARG_bitmap].u_obj; } MP_DEFINE_CONST_FUN_OBJ_KW(bitmapfilter_morph_obj, 0, bitmapfilter_morph); diff --git a/shared-bindings/board/__init__.h b/shared-bindings/board/__init__.h index 43343099730..2df4af70593 100644 --- a/shared-bindings/board/__init__.h +++ b/shared-bindings/board/__init__.h @@ -11,6 +11,16 @@ #include "shared-bindings/microcontroller/Pin.h" // for the pin definitions +// A port can inject board.EFUSE_KEY* (or other hardware key) entries into +// every board's globals table by defining CIRCUITPY_BOARD_HARDWARE_KEYS (a +// comma-terminated list of { MP_ROM_QSTR(...), MP_ROM_PTR(...) } pairs). +#if CIRCUITPY_HARDWAREKEY +#include "common-hal/hardwarekey/board.h" +#endif +#ifndef CIRCUITPY_BOARD_HARDWARE_KEYS +#define CIRCUITPY_BOARD_HARDWARE_KEYS +#endif + #if CIRCUITPY_MUTABLE_BOARD extern mp_obj_dict_t board_module_globals; #else @@ -41,8 +51,10 @@ MP_DECLARE_CONST_FUN_OBJ_0(board_uart_obj); #define CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS \ { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_board) }, \ - { MP_ROM_QSTR(MP_QSTR_board_id), MP_ROM_PTR(&board_module_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_board_id), MP_ROM_PTR(&board_module_id_obj) }, \ + CIRCUITPY_BOARD_HARDWARE_KEYS #define CIRCUITPYTHON_MUTABLE_BOARD_DICT_STANDARD_ITEMS \ { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_board) }, \ - { MP_ROM_QSTR(MP_QSTR_board_id), MP_OBJ_FROM_PTR(&board_module_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_board_id), MP_OBJ_FROM_PTR(&board_module_id_obj) }, \ + CIRCUITPY_BOARD_HARDWARE_KEYS diff --git a/shared-bindings/busio/I2C.c b/shared-bindings/busio/I2C.c index 5fa62f9df38..b619dfa1140 100644 --- a/shared-bindings/busio/I2C.c +++ b/shared-bindings/busio/I2C.c @@ -369,7 +369,7 @@ static mp_obj_t busio_i2c_writeto_then_readfrom(size_t n_args, const mp_obj_t *p int32_t in_start = args[ARG_in_start].u_int; const int32_t in_end = args[ARG_in_end].u_int; normalize_buffer_bounds(&in_start, in_end, &in_length); - mp_arg_validate_length_min(in_length, 1, MP_QSTR_out_buffer); + mp_arg_validate_length_min(in_length, 1, MP_QSTR_in_buffer); // Treat start and length in terms of bytes from now on. out_start *= out_stride_in_bytes; diff --git a/shared-bindings/busio/I2C.h b/shared-bindings/busio/I2C.h index 2978a907544..8eb581bd370 100644 --- a/shared-bindings/busio/I2C.h +++ b/shared-bindings/busio/I2C.h @@ -50,4 +50,6 @@ mp_negative_errno_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint1 uint8_t *out_data, size_t out_len, uint8_t *in_data, size_t in_len); // This is used by the supervisor to claim I2C devices indefinitely. +#if CIRCUITPY_BULK_RESET extern void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self); +#endif diff --git a/shared-bindings/busio/SPI.h b/shared-bindings/busio/SPI.h index 76ed697d665..382c91240b2 100644 --- a/shared-bindings/busio/SPI.h +++ b/shared-bindings/busio/SPI.h @@ -7,6 +7,7 @@ #pragma once #include "py/obj.h" +#include "supervisor/shared/async_flag.h" #include "common-hal/microcontroller/Pin.h" #include "common-hal/busio/SPI.h" @@ -35,6 +36,17 @@ extern void common_hal_busio_spi_unlock(busio_spi_obj_t *self); // Writes out the given data. extern bool common_hal_busio_spi_write(busio_spi_obj_t *self, const uint8_t *data, size_t len); +#if CIRCUITPY_BUSIO_SPI_ASYNC +// Start writing data and return, possibly before it has been sent. *done is set once it has been +// sent, at the latest by common_hal_busio_spi_end(). data must stay valid and unchanged, and +// the bus must not be used, until then. +extern void common_hal_busio_spi_write_start(busio_spi_obj_t *self, const uint8_t *data, size_t len, + circuitpy_async_flag_t *done); +// Finish the transfer started by a common_hal_busio_spi_*_start(), waiting if needed. Returns +// at once if there is none. +extern void common_hal_busio_spi_end(busio_spi_obj_t *self); +#endif + // Reads in len bytes while outputting the byte write_value. extern bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size_t len, uint8_t write_value); @@ -51,7 +63,9 @@ uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self); uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self); // This is used by the supervisor to claim SPI devices indefinitely. +#if CIRCUITPY_BULK_RESET extern void common_hal_busio_spi_never_reset(busio_spi_obj_t *self); +#endif extern busio_spi_obj_t *validate_obj_is_spi_bus(mp_obj_t obj_in, qstr arg_name); diff --git a/shared-bindings/busio/UART.c b/shared-bindings/busio/UART.c index 81e970a98f4..da05b226fd7 100644 --- a/shared-bindings/busio/UART.c +++ b/shared-bindings/busio/UART.c @@ -77,7 +77,7 @@ //| *New in CircuitPython 4.0:* ``timeout`` has incompatibly changed units from milliseconds to seconds. //| The new upper limit on ``timeout`` is meant to catch mistaken use of milliseconds. //| -//| **Limitations:** RS485 is not supported on SAMD, Nordic, Broadcom, Spresense, or STM. +//| **Limitations:** RS485 is not supported on SAMD, Nordic, Spresense, or STM. //| On i.MX and Raspberry Pi RP2040, RS485 support is implemented in software: //| The timing for the ``rs485_dir`` pin signal is done on a best-effort basis, and may not meet //| RS485 specifications intermittently. diff --git a/shared-bindings/busio/UART.h b/shared-bindings/busio/UART.h index c8e6755c129..4e5c8492055 100644 --- a/shared-bindings/busio/UART.h +++ b/shared-bindings/busio/UART.h @@ -47,4 +47,6 @@ extern uint32_t common_hal_busio_uart_rx_characters_available(busio_uart_obj_t * extern void common_hal_busio_uart_clear_rx_buffer(busio_uart_obj_t *self); extern bool common_hal_busio_uart_ready_to_tx(busio_uart_obj_t *self); +#if CIRCUITPY_BULK_RESET extern void common_hal_busio_uart_never_reset(busio_uart_obj_t *self); +#endif diff --git a/shared-bindings/camera/Camera.c b/shared-bindings/camera/Camera.c index f0991082e50..67e7091e01a 100644 --- a/shared-bindings/camera/Camera.c +++ b/shared-bindings/camera/Camera.c @@ -44,7 +44,7 @@ static mp_obj_t camera_make_new(const mp_obj_type_t *type, size_t n_args, size_t // No arguments mp_arg_check_num(n_args, n_kw, 0, 0, false); - camera_obj_t *self = mp_obj_malloc(camera_obj_t, &camera_type); + camera_obj_t *self = mp_obj_malloc_with_finaliser(camera_obj_t, &camera_type); common_hal_camera_construct(self); return MP_OBJ_FROM_PTR(self); } @@ -99,6 +99,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(camera_take_picture_obj, 1, camera_obj_take_picture); static const mp_rom_map_elem_t camera_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&camera_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&camera_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_take_picture), MP_ROM_PTR(&camera_take_picture_obj) }, }; static MP_DEFINE_CONST_DICT(camera_locals_dict, camera_locals_dict_table); diff --git a/shared-bindings/canio/CAN.c b/shared-bindings/canio/CAN.c index ff46eb26da2..6979922a94f 100644 --- a/shared-bindings/canio/CAN.c +++ b/shared-bindings/canio/CAN.c @@ -64,7 +64,7 @@ static mp_obj_t canio_can_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_raise_ValueError(MP_ERROR_TEXT("tx and rx cannot both be None")); } - canio_can_obj_t *self = mp_obj_malloc(canio_can_obj_t, &canio_can_type); + canio_can_obj_t *self = mp_obj_malloc_with_finaliser(canio_can_obj_t, &canio_can_type); common_hal_canio_can_construct(self, tx_pin, rx_pin, args[ARG_baudrate].u_int, args[ARG_loopback].u_bool, args[ARG_silent].u_bool); common_hal_canio_can_auto_restart_set(self, args[ARG_auto_restart].u_bool); @@ -224,8 +224,7 @@ static mp_obj_t canio_can_listen(size_t n_args, const mp_obj_t *pos_args, mp_map } float timeout = args[ARG_timeout].u_obj ? mp_obj_get_float(args[ARG_timeout].u_obj) : 10.0f; - canio_listener_obj_t *listener = m_new_obj(canio_listener_obj_t); - listener->base.type = &canio_listener_type; + canio_listener_obj_t *listener = mp_obj_malloc_with_finaliser(canio_listener_obj_t, &canio_listener_type); common_hal_canio_listener_construct(listener, self, nmatch, matches, timeout); return listener; } @@ -326,6 +325,7 @@ static const mp_rom_map_elem_t canio_can_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_auto_restart), MP_ROM_PTR(&canio_can_auto_restart_obj) }, { MP_ROM_QSTR(MP_QSTR_baudrate), MP_ROM_PTR(&canio_can_baudrate_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&canio_can_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&canio_can_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_listen), MP_ROM_PTR(&canio_can_listen_obj) }, { MP_ROM_QSTR(MP_QSTR_loopback), MP_ROM_PTR(&canio_can_loopback_obj) }, { MP_ROM_QSTR(MP_QSTR_receive_error_count), MP_ROM_PTR(&canio_can_receive_error_count_obj) }, diff --git a/shared-bindings/canio/Listener.c b/shared-bindings/canio/Listener.c index 0fd53eecbef..ccdc77e372a 100644 --- a/shared-bindings/canio/Listener.c +++ b/shared-bindings/canio/Listener.c @@ -148,6 +148,7 @@ static const mp_rom_map_elem_t canio_listener_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&canio_listener_enter_obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&canio_listener_exit_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&canio_listener_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&canio_listener_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_in_waiting), MP_ROM_PTR(&canio_listener_in_waiting_obj) }, { MP_ROM_QSTR(MP_QSTR_receive), MP_ROM_PTR(&canio_listener_receive_obj) }, { MP_ROM_QSTR(MP_QSTR_timeout), MP_ROM_PTR(&canio_listener_timeout_obj) }, diff --git a/shared-bindings/digitalio/DigitalInOut.c b/shared-bindings/digitalio/DigitalInOut.c index 58f90b0b141..827c60c661c 100644 --- a/shared-bindings/digitalio/DigitalInOut.c +++ b/shared-bindings/digitalio/DigitalInOut.c @@ -73,7 +73,7 @@ static mp_obj_t digitalio_digitalinout_make_new(const mp_obj_type_t *type, const mcu_pin_obj_t *pin = common_hal_digitalio_validate_pin(args[0]); - digitalio_digitalinout_obj_t *self = mp_obj_malloc(digitalio_digitalinout_obj_t, &digitalio_digitalinout_type); + digitalio_digitalinout_obj_t *self = mp_obj_malloc_with_finaliser(digitalio_digitalinout_obj_t, &digitalio_digitalinout_type); common_hal_digitalio_digitalinout_construct(self, pin); return MP_OBJ_FROM_PTR(self); @@ -134,6 +134,8 @@ static mp_obj_t digitalio_digitalinout_switch_to_output(size_t n_args, const mp_ digitalio_drive_mode_t drive_mode = DRIVE_MODE_PUSH_PULL; if (args[ARG_drive_mode].u_rom_obj == MP_ROM_PTR(&digitalio_drive_mode_open_drain_obj)) { drive_mode = DRIVE_MODE_OPEN_DRAIN; + } else if (args[ARG_drive_mode].u_rom_obj != MP_ROM_PTR(&digitalio_drive_mode_push_pull_obj)) { + mp_arg_error_invalid(MP_QSTR_drive_mode); } // do the transfer check_result(common_hal_digitalio_digitalinout_switch_to_output(self, args[ARG_value].u_bool, drive_mode)); @@ -271,6 +273,8 @@ static mp_obj_t digitalio_digitalinout_obj_set_drive_mode(mp_obj_t self_in, mp_o digitalio_drive_mode_t c_drive_mode = DRIVE_MODE_PUSH_PULL; if (drive_mode == MP_ROM_PTR(&digitalio_drive_mode_open_drain_obj)) { c_drive_mode = DRIVE_MODE_OPEN_DRAIN; + } else if (drive_mode != MP_ROM_PTR(&digitalio_drive_mode_push_pull_obj)) { + mp_arg_error_invalid(MP_QSTR_drive_mode); } check_result(common_hal_digitalio_digitalinout_set_drive_mode(self, c_drive_mode)); return mp_const_none; @@ -328,6 +332,7 @@ MP_PROPERTY_GETSET(digitalio_digitalio_pull_obj, static const mp_rom_map_elem_t digitalio_digitalinout_locals_dict_table[] = { // instance methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&digitalio_digitalinout_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&digitalio_digitalinout_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_switch_to_output), MP_ROM_PTR(&digitalio_digitalinout_switch_to_output_obj) }, diff --git a/shared-bindings/digitalio/DigitalInOut.h b/shared-bindings/digitalio/DigitalInOut.h index 8d3fe8c00a2..a32e31f7849 100644 --- a/shared-bindings/digitalio/DigitalInOut.h +++ b/shared-bindings/digitalio/DigitalInOut.h @@ -52,7 +52,9 @@ digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode(digitalio digitalio_drive_mode_t common_hal_digitalio_digitalinout_get_drive_mode(digitalio_digitalinout_obj_t *self); digitalinout_result_t common_hal_digitalio_digitalinout_set_pull(digitalio_digitalinout_obj_t *self, digitalio_pull_t pull); digitalio_pull_t common_hal_digitalio_digitalinout_get_pull(digitalio_digitalinout_obj_t *self); +#if CIRCUITPY_BULK_RESET void common_hal_digitalio_digitalinout_never_reset(digitalio_digitalinout_obj_t *self); +#endif digitalio_digitalinout_obj_t *assert_digitalinout(mp_obj_t obj); volatile uint32_t *common_hal_digitalio_digitalinout_get_reg(digitalio_digitalinout_obj_t *self, digitalinout_reg_op_t op, uint32_t *mask); diff --git a/shared-bindings/displayio/Bitmap.c b/shared-bindings/displayio/Bitmap.c index e543687901a..e88df39743c 100644 --- a/shared-bindings/displayio/Bitmap.c +++ b/shared-bindings/displayio/Bitmap.c @@ -55,7 +55,7 @@ static mp_obj_t displayio_bitmap_make_new(const mp_obj_type_t *type, size_t n_ar } } - displayio_bitmap_t *self = mp_obj_malloc(displayio_bitmap_t, &displayio_bitmap_type); + displayio_bitmap_t *self = mp_obj_malloc_with_finaliser(displayio_bitmap_t, &displayio_bitmap_type); common_hal_displayio_bitmap_construct(self, width, height, bits); return MP_OBJ_FROM_PTR(self); @@ -264,6 +264,7 @@ static const mp_rom_map_elem_t displayio_bitmap_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_fill), MP_ROM_PTR(&displayio_bitmap_fill_obj) }, { MP_ROM_QSTR(MP_QSTR_dirty), MP_ROM_PTR(&displayio_bitmap_dirty_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&displayio_bitmap_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&displayio_bitmap_deinit_obj) }, }; static MP_DEFINE_CONST_DICT(displayio_bitmap_locals_dict, displayio_bitmap_locals_dict_table); diff --git a/shared-bindings/displayio/OnDiskBitmap.c b/shared-bindings/displayio/OnDiskBitmap.c index cd2161fd3b0..407b9d88351 100644 --- a/shared-bindings/displayio/OnDiskBitmap.c +++ b/shared-bindings/displayio/OnDiskBitmap.c @@ -9,6 +9,7 @@ #include #include "py/runtime.h" +#include "extmod/vfs.h" #include "py/objproperty.h" #include "shared-bindings/displayio/OnDiskBitmap.h" @@ -66,12 +67,12 @@ static mp_obj_t displayio_ondiskbitmap_make_new(const mp_obj_type_t *type, size_ if (mp_obj_is_str(arg)) { arg = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), arg, MP_ROM_QSTR(MP_QSTR_rb)); } - if (!mp_obj_is_type(arg, &mp_type_fileio)) { + if (!mp_obj_is_fileio(arg)) { mp_raise_TypeError(MP_ERROR_TEXT("file must be a file opened in byte mode")); } displayio_ondiskbitmap_t *self = mp_obj_malloc(displayio_ondiskbitmap_t, &displayio_ondiskbitmap_type); - common_hal_displayio_ondiskbitmap_construct(self, MP_OBJ_TO_PTR(arg)); + common_hal_displayio_ondiskbitmap_construct(self, arg); return MP_OBJ_FROM_PTR(self); } diff --git a/shared-bindings/displayio/OnDiskBitmap.h b/shared-bindings/displayio/OnDiskBitmap.h index 229b428df85..0222399e419 100644 --- a/shared-bindings/displayio/OnDiskBitmap.h +++ b/shared-bindings/displayio/OnDiskBitmap.h @@ -7,11 +7,11 @@ #pragma once #include "shared-module/displayio/OnDiskBitmap.h" -#include "extmod/vfs_fat.h" +#include "extmod/vfs.h" extern const mp_obj_type_t displayio_ondiskbitmap_type; -void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, pyb_file_obj_t *file); +void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, mp_obj_t file); uint32_t common_hal_displayio_ondiskbitmap_get_pixel(displayio_ondiskbitmap_t *bitmap, int16_t x, int16_t y); diff --git a/shared-bindings/displayio/__init__.c b/shared-bindings/displayio/__init__.c index 970c4826476..3843117b28f 100644 --- a/shared-bindings/displayio/__init__.c +++ b/shared-bindings/displayio/__init__.c @@ -52,7 +52,6 @@ //| rgbmatrix.RGBMatrix //| | is31fl3741.IS31FL3741_FrameBuffer //| | sharpdisplay.SharpMemoryFramebuffer -//| | videocore.Framebuffer //| | picodvi.Framebuffer //| | aurora_epaper.AuroraMemoryFramebuffer //| ) diff --git a/shared-bindings/emmcio/EMMC.c b/shared-bindings/emmcio/EMMC.c index 8286daf9e20..25b0467d5e3 100644 --- a/shared-bindings/emmcio/EMMC.c +++ b/shared-bindings/emmcio/EMMC.c @@ -119,7 +119,7 @@ static mp_obj_t emmcio_emmc_make_new(const mp_obj_type_t *type, size_t n_args, s } } - emmcio_emmc_obj_t *self = mp_obj_malloc(emmcio_emmc_obj_t, &emmcio_emmc_type); + emmcio_emmc_obj_t *self = mp_obj_malloc_with_finaliser(emmcio_emmc_obj_t, &emmcio_emmc_type); // An OSError means the card itself did not come up; a ValueError means the // wiring or the hardware is unusable. int detail = 0; @@ -458,6 +458,7 @@ MP_PROPERTY_GETTER(emmcio_emmc_frequency_obj, (mp_obj_t)&emmcio_emmc_get_frequen static const mp_rom_map_elem_t emmcio_emmc_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&emmcio_emmc_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&emmcio_emmc_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&emmcio_emmc___exit___obj) }, diff --git a/shared-bindings/floppyio/__init__.c b/shared-bindings/floppyio/__init__.c index 09034b4675d..f32a0759953 100644 --- a/shared-bindings/floppyio/__init__.c +++ b/shared-bindings/floppyio/__init__.c @@ -147,10 +147,10 @@ static mp_obj_t floppyio_mfm_readinto(size_t n_args, const mp_obj_t *pos_args, m mp_buffer_info_t bufinfo_validity; uint8_t validity_buf[n_sectors]; if (args[ARG_validity].u_obj) { - mp_get_buffer_raise(args[ARG_validity].u_obj, &bufinfo_validity, MP_BUFFER_READ); + mp_get_buffer_raise(args[ARG_validity].u_obj, &bufinfo_validity, MP_BUFFER_WRITE); mp_arg_validate_length_min(bufinfo_validity.len, n_sectors, MP_QSTR_validity); if (args[ARG_clear_validity].u_bool) { - memset(validity_buf, 0, sizeof(validity_buf)); + memset(bufinfo_validity.buf, 0, n_sectors); } } else { bufinfo_validity.buf = &validity_buf; diff --git a/shared-bindings/fourwire/FourWire.h b/shared-bindings/fourwire/FourWire.h index 3e77c25434d..bbb407de027 100644 --- a/shared-bindings/fourwire/FourWire.h +++ b/shared-bindings/fourwire/FourWire.h @@ -32,6 +32,12 @@ void common_hal_fourwire_fourwire_send(mp_obj_t self, display_byte_type_t byte_t void common_hal_fourwire_fourwire_end_transaction(mp_obj_t self); +#if CIRCUITPY_BUSIO_SPI_ASYNC +void common_hal_fourwire_fourwire_send_async(mp_obj_t self, display_byte_type_t byte_type, + display_chip_select_behavior_t chip_select, const uint8_t *data, uint32_t data_length); +void common_hal_fourwire_fourwire_flush(mp_obj_t self); +#endif + // The FourWire object always lives off the MP heap. So, code must collect any pointers // back to the MP heap manually. Otherwise they'll get freed. void common_hal_fourwire_fourwire_collect_ptrs(mp_obj_t obj); diff --git a/shared-bindings/gifio/GifWriter.c b/shared-bindings/gifio/GifWriter.c index f649a9299c0..047916aceb9 100644 --- a/shared-bindings/gifio/GifWriter.c +++ b/shared-bindings/gifio/GifWriter.c @@ -54,7 +54,7 @@ static mp_obj_t gifio_gifwriter_make_new(const mp_obj_type_t *type, size_t n_arg own_file = true; } - gifio_gifwriter_t *self = mp_obj_malloc(gifio_gifwriter_t, &gifio_gifwriter_type); + gifio_gifwriter_t *self = mp_obj_malloc_with_finaliser(gifio_gifwriter_t, &gifio_gifwriter_type); shared_module_gifio_gifwriter_construct( self, file, @@ -138,6 +138,7 @@ static const mp_rom_map_elem_t gifio_gifwriter_locals_table[] = { { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&gifio_gifwriter_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&gifio_gifwriter_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_add_frame), MP_ROM_PTR(&gifio_gifwriter_add_frame_obj) }, }; diff --git a/shared-bindings/gifio/OnDiskGif.c b/shared-bindings/gifio/OnDiskGif.c index 092d9dd19cf..0ee0365f5d8 100644 --- a/shared-bindings/gifio/OnDiskGif.c +++ b/shared-bindings/gifio/OnDiskGif.c @@ -9,6 +9,7 @@ #include #include "py/runtime.h" +#include "extmod/vfs.h" #include "py/objproperty.h" #include "shared/runtime/context_manager_helpers.h" #include "shared-bindings/util.h" @@ -112,17 +113,17 @@ static mp_obj_t gifio_ondiskgif_make_new(const mp_obj_type_t *type, size_t n_arg mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_obj_t filename = all_args[0]; + mp_obj_t filename = args[ARG_filename].u_obj; if (mp_obj_is_str(filename)) { filename = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), filename, MP_ROM_QSTR(MP_QSTR_rb)); } - if (!mp_obj_is_type(filename, &mp_type_fileio)) { + if (!mp_obj_is_fileio(filename)) { mp_raise_TypeError(MP_ERROR_TEXT("file must be a file opened in byte mode")); } - gifio_ondiskgif_t *self = mp_obj_malloc(gifio_ondiskgif_t, &gifio_ondiskgif_type); - common_hal_gifio_ondiskgif_construct(self, MP_OBJ_TO_PTR(filename), args[ARG_use_palette].u_bool); + gifio_ondiskgif_t *self = mp_obj_malloc_with_finaliser(gifio_ondiskgif_t, &gifio_ondiskgif_type); + common_hal_gifio_ondiskgif_construct(self, filename, args[ARG_use_palette].u_bool); return MP_OBJ_FROM_PTR(self); } @@ -287,6 +288,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_deinit_obj, gifio_ondiskgif_obj_deinit static const mp_rom_map_elem_t gifio_ondiskgif_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&gifio_ondiskgif_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&gifio_ondiskgif_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&gifio_ondiskgif_height_obj) }, diff --git a/shared-bindings/gifio/OnDiskGif.h b/shared-bindings/gifio/OnDiskGif.h index 96e4cf08341..69833145c06 100644 --- a/shared-bindings/gifio/OnDiskGif.h +++ b/shared-bindings/gifio/OnDiskGif.h @@ -7,11 +7,11 @@ #pragma once #include "shared-module/gifio/OnDiskGif.h" -#include "extmod/vfs_fat.h" +#include "extmod/vfs.h" extern const mp_obj_type_t gifio_ondiskgif_type; -void common_hal_gifio_ondiskgif_construct(gifio_ondiskgif_t *self, pyb_file_obj_t *file, bool use_palette); +void common_hal_gifio_ondiskgif_construct(gifio_ondiskgif_t *self, mp_obj_t file, bool use_palette); uint32_t common_hal_gifio_ondiskgif_get_pixel(gifio_ondiskgif_t *bitmap, int16_t x, int16_t y); diff --git a/shared-bindings/gnss/GNSS.c b/shared-bindings/gnss/GNSS.c index 3df23979c9e..996931b66a9 100644 --- a/shared-bindings/gnss/GNSS.c +++ b/shared-bindings/gnss/GNSS.c @@ -62,7 +62,7 @@ static mp_obj_t gnss_make_new(const mp_obj_type_t *type, size_t n_args, size_t n mp_raise_TypeError(MP_ERROR_TEXT("System entry must be gnss.SatelliteSystem")); } - gnss_obj_t *self = mp_obj_malloc(gnss_obj_t, &gnss_type); + gnss_obj_t *self = mp_obj_malloc_with_finaliser(gnss_obj_t, &gnss_type); common_hal_gnss_construct(self, selection); return MP_OBJ_FROM_PTR(self); @@ -164,6 +164,7 @@ MP_PROPERTY_GETTER(gnss_fix_obj, static const mp_rom_map_elem_t gnss_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&gnss_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&gnss_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&gnss_update_obj) }, { MP_ROM_QSTR(MP_QSTR_latitude), MP_ROM_PTR(&gnss_latitude_obj) }, diff --git a/shared-bindings/hardwarekey/HardwareKey.c b/shared-bindings/hardwarekey/HardwareKey.c new file mode 100644 index 00000000000..55789789aa2 --- /dev/null +++ b/shared-bindings/hardwarekey/HardwareKey.c @@ -0,0 +1,102 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#include "py/objproperty.h" +#include "py/runtime.h" + +#include "shared-bindings/hardwarekey/__init__.h" +#include "shared-bindings/hardwarekey/HardwareKey.h" + +//| class HardwareKey: +//| """A key held in a hardware key store, usable but not readable. +//| +//| This class cannot be instantiated. Every hardware key slot the board has +//| is exposed as a fixed `HardwareKey` in :mod:`board` -- for example +//| ``board.EFUSE_KEY0`` -- just like pins. A slot with no key burned into it +//| still has a `HardwareKey` object; its `purpose` is `hardwarekey.Purpose.UNUSED`. +//| +//| Compute a MAC with a key by passing it to `hmac.new()` in place of a +//| ``bytes`` key. +//| +//| On espressif the slots are the eFuse key blocks (``BLOCK_KEY0`` - +//| ``BLOCK_KEY5``); a slot is usable only if its block was burned with +//| purpose ``HMAC_UP``.""" +//| + +static void hardwarekey_hardwarekey_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + hardwarekey_hardwarekey_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->name != MP_QSTRnull) { + mp_printf(print, "", self->name); + } else { + mp_printf(print, "", (int)self->key_slot); + } +} + +//| def __bool__(self) -> bool: +//| """``False`` if `purpose` is `hardwarekey.Purpose.UNUSED`, ``True`` otherwise. +//| This lets you write ``if key:`` to check whether a slot is usable.""" +//| ... +static mp_obj_t hardwarekey_hardwarekey_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + hardwarekey_hardwarekey_obj_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_BOOL: + return mp_obj_new_bool(common_hal_hardwarekey_hardwarekey_get_purpose(self) != HARDWAREKEY_PURPOSE_UNUSED); + default: + return MP_OBJ_NULL; // op not supported + } +} + +//| key_slot: int +//| """The port-defined key identifier this handle is bound to. On espressif, +//| the eFuse key block index. (read-only)""" +static mp_obj_t hardwarekey_hardwarekey_get_key_slot(mp_obj_t self_in) { + hardwarekey_hardwarekey_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(common_hal_hardwarekey_hardwarekey_get_key_slot(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(hardwarekey_hardwarekey_get_key_slot_obj, hardwarekey_hardwarekey_get_key_slot); +MP_PROPERTY_GETTER(hardwarekey_hardwarekey_key_slot_obj, (mp_obj_t)&hardwarekey_hardwarekey_get_key_slot_obj); + +//| purpose: Purpose +//| """What this key slot is provisioned for -- `hardwarekey.Purpose.HMAC_UP` or +//| `hardwarekey.Purpose.UNUSED`. (read-only)""" +static mp_obj_t hardwarekey_hardwarekey_get_purpose(mp_obj_t self_in) { + hardwarekey_hardwarekey_obj_t *self = MP_OBJ_TO_PTR(self_in); + return cp_enum_find(&hardwarekey_purpose_type, common_hal_hardwarekey_hardwarekey_get_purpose(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(hardwarekey_hardwarekey_get_purpose_obj, hardwarekey_hardwarekey_get_purpose); +MP_PROPERTY_GETTER(hardwarekey_hardwarekey_purpose_obj, (mp_obj_t)&hardwarekey_hardwarekey_get_purpose_obj); + +//| exportable: bool +//| """Whether the raw key bytes can ever leave the hardware. Always +//| informational. +//| +//| On espressif this is ``False`` once the key block's ``RD_DIS`` eFuse +//| bit is set (which ``espefuse.py`` does by default). It is meant for +//| manufacturing-time self-test code to confirm a key block was burned as +//| expected. (read-only)""" +//| +static mp_obj_t hardwarekey_hardwarekey_get_exportable(mp_obj_t self_in) { + hardwarekey_hardwarekey_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(common_hal_hardwarekey_hardwarekey_get_exportable(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(hardwarekey_hardwarekey_get_exportable_obj, hardwarekey_hardwarekey_get_exportable); +MP_PROPERTY_GETTER(hardwarekey_hardwarekey_exportable_obj, (mp_obj_t)&hardwarekey_hardwarekey_get_exportable_obj); + +static const mp_rom_map_elem_t hardwarekey_hardwarekey_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_key_slot), MP_ROM_PTR(&hardwarekey_hardwarekey_key_slot_obj) }, + { MP_ROM_QSTR(MP_QSTR_purpose), MP_ROM_PTR(&hardwarekey_hardwarekey_purpose_obj) }, + { MP_ROM_QSTR(MP_QSTR_exportable), MP_ROM_PTR(&hardwarekey_hardwarekey_exportable_obj) }, +}; +static MP_DEFINE_CONST_DICT(hardwarekey_hardwarekey_locals_dict, hardwarekey_hardwarekey_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + hardwarekey_hardwarekey_type, + MP_QSTR_HardwareKey, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + print, hardwarekey_hardwarekey_print, + unary_op, hardwarekey_hardwarekey_unary_op, + locals_dict, &hardwarekey_hardwarekey_locals_dict + ); diff --git a/shared-bindings/hardwarekey/HardwareKey.h b/shared-bindings/hardwarekey/HardwareKey.h new file mode 100644 index 00000000000..91bdfd3c633 --- /dev/null +++ b/shared-bindings/hardwarekey/HardwareKey.h @@ -0,0 +1,15 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +// Object struct and the common_hal_* accessors. +#include "shared-module/hardwarekey/HardwareKey.h" + +// Type object used in Python. Shared between ports. +extern const mp_obj_type_t hardwarekey_hardwarekey_type; diff --git a/shared-bindings/hardwarekey/__init__.c b/shared-bindings/hardwarekey/__init__.c new file mode 100644 index 00000000000..e8802722b58 --- /dev/null +++ b/shared-bindings/hardwarekey/__init__.c @@ -0,0 +1,65 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#include "py/enum.h" +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/hardwarekey/__init__.h" +#include "shared-bindings/hardwarekey/HardwareKey.h" + +//| """Cryptographic operations with keys held in hardware +//| +//| The ``hardwarekey`` module exposes keys that live in a hardware key store -- +//| eFuse, a key manager, a secure element -- and can be *used* but never read +//| back. Application code can compute a MAC with the key; there is no API to +//| read the raw key bytes, and no API to write or burn keys. Provisioning a key +//| is a manufacturing-time step done with vendor tools (for example +//| ``espefuse.py`` on Espressif chips). +//| +//| `HardwareKey` objects are not created by application code. Every hardware key +//| slot the board has is exposed as a fixed object in :mod:`board` (for example +//| ``board.EFUSE_KEY0``), in the same way that pins are. Compute a MAC with one +//| by passing it to `hmac.new()`. +//| """ + +MAKE_ENUM_VALUE(hardwarekey_purpose_type, hardwarekey_purpose, HMAC_UP, HARDWAREKEY_PURPOSE_HMAC); +MAKE_ENUM_VALUE(hardwarekey_purpose_type, hardwarekey_purpose, UNUSED, HARDWAREKEY_PURPOSE_UNUSED); + +//| class Purpose: +//| """What a hardware key slot is provisioned for. Instances are singletons; +//| compare with ``is``.""" +//| +//| HMAC_UP: object +//| """The slot holds an HMAC key. It can be used with `hmac.new()`.""" +//| +//| UNUSED: object +//| """No key is burned into the slot (or it is burned for something this module +//| does not expose). The slot's `HardwareKey` still exists but cannot be used.""" +//| +MAKE_ENUM_MAP(hardwarekey_purpose) { + MAKE_ENUM_MAP_ENTRY(hardwarekey_purpose, HMAC_UP), + MAKE_ENUM_MAP_ENTRY(hardwarekey_purpose, UNUSED), +}; +static MP_DEFINE_CONST_DICT(hardwarekey_purpose_locals_dict, hardwarekey_purpose_locals_table); + +MAKE_PRINTER(hardwarekey, hardwarekey_purpose); + +MAKE_ENUM_TYPE(hardwarekey, Purpose, hardwarekey_purpose); + +static const mp_rom_map_elem_t hardwarekey_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_hardwarekey) }, + { MP_ROM_QSTR(MP_QSTR_HardwareKey), MP_ROM_PTR(&hardwarekey_hardwarekey_type) }, + { MP_ROM_QSTR(MP_QSTR_Purpose), MP_ROM_PTR(&hardwarekey_purpose_type) }, +}; +static MP_DEFINE_CONST_DICT(hardwarekey_module_globals, hardwarekey_module_globals_table); + +const mp_obj_module_t hardwarekey_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&hardwarekey_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_hardwarekey, hardwarekey_module); diff --git a/shared-bindings/hardwarekey/__init__.h b/shared-bindings/hardwarekey/__init__.h new file mode 100644 index 00000000000..2f7a72b3e31 --- /dev/null +++ b/shared-bindings/hardwarekey/__init__.h @@ -0,0 +1,14 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/enum.h" +#include "py/obj.h" + +#include "shared-module/hardwarekey/HardwareKey.h" + +extern const mp_obj_type_t hardwarekey_purpose_type; diff --git a/shared-bindings/hmac/HMAC.c b/shared-bindings/hmac/HMAC.c new file mode 100644 index 00000000000..a5beeba8378 --- /dev/null +++ b/shared-bindings/hmac/HMAC.c @@ -0,0 +1,140 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#include + +#include "shared-bindings/hmac/HMAC.h" + +#include "py/objproperty.h" +#include "py/objstr.h" +#include "py/runtime.h" + +//| class HMAC: +//| """An HMAC object, in progress. Created by `hmac.new()`; it has no user-visible +//| constructor.""" +//| + +//| def update(self, msg: ReadableBuffer) -> None: +//| """Feed more data into the HMAC. +//| +//| Raises `RuntimeError` if called after `digest()` or `hexdigest()` -- unlike +//| CPython's ``hmac``, this cannot be resumed once a digest has been taken.""" +//| ... +mp_obj_t hmac_hmac_update(mp_obj_t self_in, mp_obj_t buf_in) { + mp_check_self(mp_obj_is_type(self_in, &hmac_hmac_type)); + hmac_hmac_obj_t *self = MP_OBJ_TO_PTR(self_in); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + + common_hal_hmac_update(self, bufinfo.buf, bufinfo.len); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(hmac_hmac_update_obj, hmac_hmac_update); + +//| def digest(self) -> bytes: +//| """Return the HMAC of the data fed so far, as ``digest_size`` bytes. +//| +//| The first call finishes the underlying MAC computation and caches the +//| result; later calls just return the cached bytes. `update()` can no +//| longer be called after this, unlike CPython's ``hmac``.""" +//| ... +static mp_obj_t hmac_hmac_digest(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &hmac_hmac_type)); + hmac_hmac_obj_t *self = MP_OBJ_TO_PTR(self_in); + + size_t size = common_hal_hmac_get_digest_size(self); + mp_obj_t obj = mp_obj_new_bytes_of_zeros(size); + mp_obj_str_t *o = MP_OBJ_TO_PTR(obj); + + common_hal_hmac_digest(self, (uint8_t *)o->data, size); + return obj; +} +static MP_DEFINE_CONST_FUN_OBJ_1(hmac_hmac_digest_obj, hmac_hmac_digest); + +//| def hexdigest(self) -> str: +//| """Like `digest()` but returns the MAC as a string of hexadecimal digits.""" +//| ... +static mp_obj_t hmac_hmac_hexdigest(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &hmac_hmac_type)); + hmac_hmac_obj_t *self = MP_OBJ_TO_PTR(self_in); + + size_t size = common_hal_hmac_get_digest_size(self); + uint8_t digest[PSA_HASH_MAX_SIZE]; + common_hal_hmac_digest(self, digest, size); + + vstr_t vstr; + vstr_init_len(&vstr, size * 2); + for (size_t i = 0; i < size; i++) { + vstr.buf[i * 2] = nibble_to_hex_lower[digest[i] >> 4]; + vstr.buf[i * 2 + 1] = nibble_to_hex_lower[digest[i] & 0xf]; + } + return mp_obj_new_str_from_vstr(&vstr); +} +static MP_DEFINE_CONST_FUN_OBJ_1(hmac_hmac_hexdigest_obj, hmac_hmac_hexdigest); + +//| def copy(self) -> HMAC: +//| """Not supported; always raises `NotImplementedError`. +//| +//| PSA Crypto's multipart MAC API has no way to duplicate an in-progress MAC +//| operation (unlike a plain hash, which can be cloned), so this HMAC object +//| cannot be copied.""" +//| ... +static mp_obj_t hmac_hmac_copy(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &hmac_hmac_type)); + mp_raise_NotImplementedError(NULL); +} +static MP_DEFINE_CONST_FUN_OBJ_1(hmac_hmac_copy_obj, hmac_hmac_copy); + +//| digest_size: int +//| """The size of the MAC in bytes (32 for sha256, 20 for sha1). (read-only)""" +static mp_obj_t hmac_hmac_get_digest_size(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &hmac_hmac_type)); + hmac_hmac_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(common_hal_hmac_get_digest_size(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(hmac_hmac_get_digest_size_obj, hmac_hmac_get_digest_size); +MP_PROPERTY_GETTER(hmac_hmac_digest_size_obj, (mp_obj_t)&hmac_hmac_get_digest_size_obj); + +//| block_size: int +//| """The internal block size of the hash algorithm in bytes. (read-only)""" +static mp_obj_t hmac_hmac_get_block_size(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &hmac_hmac_type)); + hmac_hmac_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(common_hal_hmac_get_block_size(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(hmac_hmac_get_block_size_obj, hmac_hmac_get_block_size); +MP_PROPERTY_GETTER(hmac_hmac_block_size_obj, (mp_obj_t)&hmac_hmac_get_block_size_obj); + +//| name: str +//| """The canonical name of this HMAC, e.g. ``"hmac-sha256"``. (read-only)""" +//| +static mp_obj_t hmac_hmac_get_name(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &hmac_hmac_type)); + hmac_hmac_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *name = common_hal_hmac_get_name(self); + return mp_obj_new_str(name, strlen(name)); +} +MP_DEFINE_CONST_FUN_OBJ_1(hmac_hmac_get_name_obj, hmac_hmac_get_name); +MP_PROPERTY_GETTER(hmac_hmac_name_obj, (mp_obj_t)&hmac_hmac_get_name_obj); + +static const mp_rom_map_elem_t hmac_hmac_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&hmac_hmac_update_obj) }, + { MP_ROM_QSTR(MP_QSTR_digest), MP_ROM_PTR(&hmac_hmac_digest_obj) }, + { MP_ROM_QSTR(MP_QSTR_hexdigest), MP_ROM_PTR(&hmac_hmac_hexdigest_obj) }, + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&hmac_hmac_copy_obj) }, + { MP_ROM_QSTR(MP_QSTR_digest_size), MP_ROM_PTR(&hmac_hmac_digest_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_block_size), MP_ROM_PTR(&hmac_hmac_block_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&hmac_hmac_name_obj) }, +}; +static MP_DEFINE_CONST_DICT(hmac_hmac_locals_dict, hmac_hmac_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + hmac_hmac_type, + MP_QSTR_HMAC, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + locals_dict, &hmac_hmac_locals_dict + ); diff --git a/shared-bindings/hmac/HMAC.h b/shared-bindings/hmac/HMAC.h new file mode 100644 index 00000000000..49006a131ec --- /dev/null +++ b/shared-bindings/hmac/HMAC.h @@ -0,0 +1,25 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +#include "shared-module/hmac/__init__.h" + +extern const mp_obj_type_t hmac_hmac_type; + +// Shared with __init__.c so hmac.new() can feed the initial msg argument. +mp_obj_t hmac_hmac_update(mp_obj_t self_in, mp_obj_t buf_in); + +void common_hal_hmac_new(hmac_hmac_obj_t *self, const uint8_t *key, size_t key_len, + psa_key_id_t borrowed_key_id, psa_algorithm_t hash_alg); +void common_hal_hmac_update(hmac_hmac_obj_t *self, const uint8_t *data, size_t data_len); +void common_hal_hmac_digest(hmac_hmac_obj_t *self, uint8_t *out, size_t out_len); +size_t common_hal_hmac_get_digest_size(hmac_hmac_obj_t *self); +size_t common_hal_hmac_get_block_size(hmac_hmac_obj_t *self); +// Returns "hmac-sha256" / "hmac-sha1" for the .name property (CPython format). +const char *common_hal_hmac_get_name(hmac_hmac_obj_t *self); diff --git a/shared-bindings/hmac/__init__.c b/shared-bindings/hmac/__init__.c new file mode 100644 index 00000000000..f262eb07955 --- /dev/null +++ b/shared-bindings/hmac/__init__.c @@ -0,0 +1,147 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#include "py/obj.h" +#include "py/objstr.h" +#include "py/runtime.h" + +#include "shared-bindings/hmac/__init__.h" +#include "shared-bindings/hmac/HMAC.h" +#include "shared-module/hmac/__init__.h" + +#if CIRCUITPY_HARDWAREKEY +#include "shared-bindings/hardwarekey/HardwareKey.h" +#endif + +//| """Keyed hashing for message authentication +//| +//| |see_cpython_module| :mod:`cpython:hmac`. +//| +//| The key may be a ``bytes``-like object or, where the port provides them, a +//| ``hardwarekey.HardwareKey`` -- so the same code works with a key in flash +//| during development and a key held in hardware in production. The one +//| exception: an empty message raises `ValueError` with a +//| ``hardwarekey.HardwareKey`` (a hardware driver limitation), where it is +//| valid with a ``bytes`` key. +//| +//| Only ``"sha256"`` and ``"sha1"`` are supported for ``digestmod``. +//| """ +//| + +static psa_algorithm_t hash_alg_from_digestmod(mp_obj_t digestmod) { + const char *name = mp_obj_str_get_str(digestmod); + psa_algorithm_t hash_alg; + if (!hmac_hash_alg_from_name(name, &hash_alg)) { + mp_raise_ValueError(MP_ERROR_TEXT("Unsupported hash algorithm")); + } + return hash_alg; +} + +static hmac_hmac_obj_t *hmac_new_internal(mp_obj_t key_in, psa_algorithm_t hash_alg) { + hmac_hmac_obj_t *self = mp_obj_malloc(hmac_hmac_obj_t, &hmac_hmac_type); + + #if CIRCUITPY_HARDWAREKEY + if (mp_obj_is_type(key_in, &hardwarekey_hardwarekey_type)) { + psa_key_id_t key_id = common_hal_hardwarekey_hardwarekey_get_key_id(MP_OBJ_TO_PTR(key_in)); + if (key_id == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("hardware key slot is unused")); + } + common_hal_hmac_new(self, NULL, 0, key_id, hash_alg); + return self; + } + #endif + + mp_buffer_info_t keyinfo; + mp_get_buffer_raise(key_in, &keyinfo, MP_BUFFER_READ); + + common_hal_hmac_new(self, keyinfo.buf, keyinfo.len, 0, hash_alg); + return self; +} + +//| def new( +//| key: ReadableBuffer | hardwarekey.HardwareKey, +//| msg: ReadableBuffer = b"", +//| digestmod: str = ..., +//| ) -> HMAC: +//| """Create a new HMAC object. +//| +//| :param key: the secret key -- a ``bytes``-like object, or a +//| ``hardwarekey.HardwareKey`` on ports that provide it +//| :param ReadableBuffer msg: initial data to authenticate; add more with `HMAC.update()` +//| :param str digestmod: the digest name, ``"sha256"`` or ``"sha1"``. Required. +//| """ +//| ... +//| +static mp_obj_t hmac_new(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_key, ARG_msg, ARG_digestmod }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_key, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_msg, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_digestmod, MP_ARG_REQUIRED | MP_ARG_OBJ }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + psa_algorithm_t hash_alg = hash_alg_from_digestmod(args[ARG_digestmod].u_obj); + hmac_hmac_obj_t *self = hmac_new_internal(args[ARG_key].u_obj, hash_alg); + + if (args[ARG_msg].u_obj != mp_const_none) { + hmac_hmac_update(MP_OBJ_FROM_PTR(self), args[ARG_msg].u_obj); + } + return MP_OBJ_FROM_PTR(self); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(hmac_new_obj, 1, hmac_new); + +//| def digest( +//| key: ReadableBuffer | hardwarekey.HardwareKey, msg: ReadableBuffer, digest: str +//| ) -> bytes: +//| """Return the HMAC of ``msg`` under ``key`` for the named ``digest``, in one call. +//| +//| Equivalent to ``new(key, msg, digestmod=digest).digest()`` but does not build an +//| intermediate object.""" +//| ... +//| +static mp_obj_t hmac_digest(mp_obj_t key_in, mp_obj_t msg_in, mp_obj_t digest_in) { + psa_algorithm_t hash_alg = hash_alg_from_digestmod(digest_in); + hmac_hmac_obj_t *self = hmac_new_internal(key_in, hash_alg); + hmac_hmac_update(MP_OBJ_FROM_PTR(self), msg_in); + + size_t size = common_hal_hmac_get_digest_size(self); + mp_obj_t obj = mp_obj_new_bytes_of_zeros(size); + mp_obj_str_t *o = MP_OBJ_TO_PTR(obj); + common_hal_hmac_digest(self, (uint8_t *)o->data, size); + return obj; +} +static MP_DEFINE_CONST_FUN_OBJ_3(hmac_digest_obj, hmac_digest); + +//| def compare_digest(a: ReadableBuffer, b: ReadableBuffer) -> bool: +//| """Return ``a == b`` using a constant-time comparison, to avoid leaking timing +//| information about a MAC check.""" +//| ... +//| +static mp_obj_t hmac_compare_digest(mp_obj_t a_in, mp_obj_t b_in) { + mp_buffer_info_t a, b; + mp_get_buffer_raise(a_in, &a, MP_BUFFER_READ); + mp_get_buffer_raise(b_in, &b, MP_BUFFER_READ); + return mp_obj_new_bool(common_hal_hmac_compare_digest(a.buf, a.len, b.buf, b.len)); +} +static MP_DEFINE_CONST_FUN_OBJ_2(hmac_compare_digest_obj, hmac_compare_digest); + +static const mp_rom_map_elem_t hmac_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_hmac) }, + { MP_ROM_QSTR(MP_QSTR_new), MP_ROM_PTR(&hmac_new_obj) }, + { MP_ROM_QSTR(MP_QSTR_digest), MP_ROM_PTR(&hmac_digest_obj) }, + { MP_ROM_QSTR(MP_QSTR_compare_digest), MP_ROM_PTR(&hmac_compare_digest_obj) }, + { MP_ROM_QSTR(MP_QSTR_HMAC), MP_ROM_PTR(&hmac_hmac_type) }, +}; +static MP_DEFINE_CONST_DICT(hmac_module_globals, hmac_module_globals_table); + +const mp_obj_module_t hmac_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&hmac_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_hmac, hmac_module); diff --git a/ports/broadcom/common-hal/sdioio/__init__.h b/shared-bindings/hmac/__init__.h similarity index 57% rename from ports/broadcom/common-hal/sdioio/__init__.h rename to shared-bindings/hmac/__init__.h index c9069db9fd7..9ea3521ab1e 100644 --- a/ports/broadcom/common-hal/sdioio/__init__.h +++ b/shared-bindings/hmac/__init__.h @@ -1,7 +1,9 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey // // SPDX-License-Identifier: MIT #pragma once + +#include "shared-bindings/hmac/HMAC.h" diff --git a/shared-bindings/i2cioexpander/IOExpander.c b/shared-bindings/i2cioexpander/IOExpander.c index 3efa0fd1868..cae2add3de7 100644 --- a/shared-bindings/i2cioexpander/IOExpander.c +++ b/shared-bindings/i2cioexpander/IOExpander.c @@ -95,7 +95,7 @@ static mp_obj_t i2cioexpander_ioexpander_make_new(const mp_obj_type_t *type, siz } i2cioexpander_ioexpander_obj_t *self = - mp_obj_malloc(i2cioexpander_ioexpander_obj_t, &i2cioexpander_ioexpander_type); + mp_obj_malloc_with_finaliser(i2cioexpander_ioexpander_obj_t, &i2cioexpander_ioexpander_type); common_hal_i2cioexpander_ioexpander_construct( self, i2c, @@ -227,6 +227,7 @@ MP_PROPERTY_GETTER(i2cioexpander_ioexpander_pins_obj, static const mp_rom_map_elem_t i2cioexpander_ioexpander_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&i2cioexpander_ioexpander_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&i2cioexpander_ioexpander_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&i2cioexpander_ioexpander___exit___obj) }, diff --git a/shared-bindings/imagecapture/ParallelImageCapture.c b/shared-bindings/imagecapture/ParallelImageCapture.c index 427eaeb3793..025a2a225fb 100644 --- a/shared-bindings/imagecapture/ParallelImageCapture.c +++ b/shared-bindings/imagecapture/ParallelImageCapture.c @@ -48,16 +48,16 @@ static mp_obj_t imagecapture_parallelimagecapture_make_new(const mp_obj_type_t * MP_STATIC_ASSERT(MP_ARRAY_SIZE(allowed_args) == NUM_ARGS); mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - uint8_t pins[32]; + const mcu_pin_obj_t *pins[32]; uint8_t pin_count; - validate_pins(MP_QSTR_data, pins, MP_ARRAY_SIZE(pins), args[ARG_data_pins].u_obj, &pin_count); + validate_list_is_free_pins(MP_QSTR_data, pins, MP_ARRAY_SIZE(pins), args[ARG_data_pins].u_obj, &pin_count); const mcu_pin_obj_t *clock = validate_obj_is_free_pin(args[ARG_clock].u_obj, MP_QSTR_clock); const mcu_pin_obj_t *vsync = validate_obj_is_free_pin_or_none(args[ARG_vsync].u_obj, MP_QSTR_vsync); const mcu_pin_obj_t *href = validate_obj_is_free_pin_or_none(args[ARG_href].u_obj, MP_QSTR_href); imagecapture_parallelimagecapture_obj_t *self = - mp_obj_malloc(imagecapture_parallelimagecapture_obj_t, &imagecapture_parallelimagecapture_type); + mp_obj_malloc_with_finaliser(imagecapture_parallelimagecapture_obj_t, &imagecapture_parallelimagecapture_type); common_hal_imagecapture_parallelimagecapture_construct(self, pins, pin_count, clock, vsync, href); return self; @@ -158,6 +158,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(imagecapture_parallelimagecapture_deinit_obj, i static const mp_rom_map_elem_t imagecapture_parallelimagecapture_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&imagecapture_parallelimagecapture_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&imagecapture_parallelimagecapture_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/imagecapture/ParallelImageCapture.h b/shared-bindings/imagecapture/ParallelImageCapture.h index fddd2cb2763..ed782e246d4 100644 --- a/shared-bindings/imagecapture/ParallelImageCapture.h +++ b/shared-bindings/imagecapture/ParallelImageCapture.h @@ -13,7 +13,7 @@ extern const mp_obj_type_t imagecapture_parallelimagecapture_type; // if only the first element of data_pins is non-NULL, the pins are sequential in microcontroller pin numbering. void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_parallelimagecapture_obj_t *self, - const uint8_t data_pins[], + const mcu_pin_obj_t **data_pins, uint8_t data_count, const mcu_pin_obj_t *data_clock, const mcu_pin_obj_t *vertical_sync, diff --git a/shared-bindings/ipaddress/IPv4Address.c b/shared-bindings/ipaddress/IPv4Address.c index 7705e500e7e..6c11f528506 100644 --- a/shared-bindings/ipaddress/IPv4Address.c +++ b/shared-bindings/ipaddress/IPv4Address.c @@ -38,8 +38,10 @@ static mp_obj_t ipaddress_ipv4address_make_new(const mp_obj_type_t *type, size_t uint32_t value; uint8_t *buf = NULL; - if (mp_obj_get_int_maybe(address, (mp_int_t *)&value)) { + mp_int_t int_value; + if (mp_obj_get_int_maybe(address, &int_value)) { // We're done. + value = (uint32_t)int_value; buf = (uint8_t *)&value; } else if (mp_obj_is_str(address)) { GET_STR_DATA_LEN(address, str_data, str_len); @@ -49,12 +51,13 @@ static mp_obj_t ipaddress_ipv4address_make_new(const mp_obj_type_t *type, size_t buf = (uint8_t *)&value; } else { mp_buffer_info_t buf_info; - if (mp_get_buffer(address, &buf_info, MP_BUFFER_READ)) { - if (buf_info.len != 4) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("Address must be %d bytes long"), 4); - } - buf = buf_info.buf; + if (!mp_get_buffer(address, &buf_info, MP_BUFFER_READ)) { + mp_arg_error_invalid(MP_QSTR_address); + } + if (buf_info.len != 4) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Address must be %d bytes long"), 4); } + buf = buf_info.buf; } diff --git a/shared-bindings/ipaddress/__init__.c b/shared-bindings/ipaddress/__init__.c index 60bc6226ba1..db8d22948a1 100644 --- a/shared-bindings/ipaddress/__init__.c +++ b/shared-bindings/ipaddress/__init__.c @@ -30,7 +30,7 @@ bool ipaddress_parse_ipv4address(const char *str_data, size_t str_len, uint32_t period_count++; } } - if (period_count > 3) { + if (period_count != 3) { return false; } @@ -50,7 +50,13 @@ bool ipaddress_parse_ipv4address(const char *str_data, size_t str_len, uint32_t } last_period = period_index[i] + 1; if (ip_out != NULL) { + if (!mp_obj_is_small_int(octet)) { + return false; + } mp_int_t int_octet = MP_OBJ_SMALL_INT_VALUE(octet); + if (int_octet < 0 || int_octet > 255) { + return false; + } *ip_out |= int_octet << (i * 8); } } diff --git a/shared-bindings/is31fl3741/FrameBuffer.c b/shared-bindings/is31fl3741/FrameBuffer.c index b59c7360f8b..a1171ff7074 100644 --- a/shared-bindings/is31fl3741/FrameBuffer.c +++ b/shared-bindings/is31fl3741/FrameBuffer.c @@ -198,6 +198,7 @@ MP_PROPERTY_GETTER(is31fl3741_framebuffer_height_obj, static const mp_rom_map_elem_t is31fl3741_framebuffer_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&is31fl3741_framebuffer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&is31fl3741_framebuffer_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_brightness), MP_ROM_PTR(&is31fl3741_framebuffer_brightness_obj) }, { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&is31fl3741_framebuffer_refresh_obj) }, { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&is31fl3741_framebuffer_width_obj) }, diff --git a/shared-bindings/is31fl3741/IS31FL3741.c b/shared-bindings/is31fl3741/IS31FL3741.c index 71c4437a1d2..a51a1e44e79 100644 --- a/shared-bindings/is31fl3741/IS31FL3741.c +++ b/shared-bindings/is31fl3741/IS31FL3741.c @@ -37,7 +37,7 @@ static mp_obj_t is31fl3741_IS31FL3741_make_new(const mp_obj_type_t *type, size_t mp_obj_t i2c = mp_arg_validate_type(args[ARG_i2c].u_obj, &busio_i2c_type, MP_QSTR_i2c_bus); - is31fl3741_IS31FL3741_obj_t *self = mp_obj_malloc(is31fl3741_IS31FL3741_obj_t, &is31fl3741_IS31FL3741_type); + is31fl3741_IS31FL3741_obj_t *self = mp_obj_malloc_with_finaliser(is31fl3741_IS31FL3741_obj_t, &is31fl3741_IS31FL3741_type); common_hal_is31fl3741_IS31FL3741_construct(self, MP_OBJ_TO_PTR(i2c), args[ARG_addr].u_int @@ -149,6 +149,7 @@ MP_DEFINE_CONST_FUN_OBJ_3(is31fl3741_IS31FL3741_write_obj, is31fl3741_IS31FL3741 static const mp_rom_map_elem_t is31fl3741_IS31FL3741_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&is31fl3741_IS31FL3741_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&is31fl3741_IS31FL3741_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_write), (mp_obj_t)&is31fl3741_IS31FL3741_write_obj }, { MP_ROM_QSTR(MP_QSTR_reset), (mp_obj_t)&is31fl3741_IS31FL3741_reset_obj }, { MP_ROM_QSTR(MP_QSTR_enable), (mp_obj_t)&is31fl3741_IS31FL3741_enable_obj }, diff --git a/shared-bindings/keypad/KeyMatrix.c b/shared-bindings/keypad/KeyMatrix.c index 01416a2f8a1..262c86fd77e 100644 --- a/shared-bindings/keypad/KeyMatrix.c +++ b/shared-bindings/keypad/KeyMatrix.c @@ -122,7 +122,7 @@ static mp_obj_t keypad_keymatrix_make_new(const mp_obj_type_t *type, size_t n_ar column_pins_array[column] = pin; } - keypad_keymatrix_obj_t *self = mp_obj_malloc(keypad_keymatrix_obj_t, &keypad_keymatrix_type); + keypad_keymatrix_obj_t *self = mp_obj_malloc_with_finaliser(keypad_keymatrix_obj_t, &keypad_keymatrix_type); common_hal_keypad_keymatrix_construct(self, num_row_pins, row_pins_array, num_column_pins, column_pins_array, args[ARG_columns_to_anodes].u_bool, interval, max_events, debounce_threshold); return MP_OBJ_FROM_PTR(self); @@ -195,7 +195,7 @@ static mp_obj_t keypad_keymatrix_key_number_to_row_column(mp_obj_t self_in, mp_o const mp_uint_t key_number = (mp_uint_t)mp_arg_validate_int_range( mp_obj_get_int(key_number_in), - 0, (mp_int_t)common_hal_keypad_generic_get_key_count(self), + 0, (mp_int_t)common_hal_keypad_generic_get_key_count(self) - 1, MP_QSTR_key_number); mp_uint_t row; @@ -221,10 +221,10 @@ static mp_obj_t keypad_keymatrix_row_column_to_key_number(mp_obj_t self_in, mp_o check_for_deinit(self); const mp_uint_t row = (mp_uint_t)mp_arg_validate_int_range( - mp_obj_get_int(row_in), 0, (mp_int_t)common_hal_keypad_keymatrix_get_row_count(self), MP_QSTR_row); + mp_obj_get_int(row_in), 0, (mp_int_t)common_hal_keypad_keymatrix_get_row_count(self) - 1, MP_QSTR_row); const mp_int_t column = (mp_uint_t)mp_arg_validate_int_range( - mp_obj_get_int(column_in), 0, (mp_int_t)common_hal_keypad_keymatrix_get_column_count(self), MP_QSTR_column); + mp_obj_get_int(column_in), 0, (mp_int_t)common_hal_keypad_keymatrix_get_column_count(self) - 1, MP_QSTR_column); return MP_OBJ_NEW_SMALL_INT( (mp_int_t)common_hal_keypad_keymatrix_row_column_to_key_number(self, row, column)); @@ -239,6 +239,7 @@ MP_DEFINE_CONST_FUN_OBJ_3(keypad_keymatrix_row_column_to_key_number_obj, keypad_ static const mp_rom_map_elem_t keypad_keymatrix_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&keypad_keymatrix_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&keypad_keymatrix_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/keypad/Keys.c b/shared-bindings/keypad/Keys.c index 5fd065fc627..0570e735f16 100644 --- a/shared-bindings/keypad/Keys.c +++ b/shared-bindings/keypad/Keys.c @@ -111,7 +111,7 @@ static mp_obj_t keypad_keys_make_new(const mp_obj_type_t *type, size_t n_args, s validate_obj_is_free_pin(mp_obj_subscr(pins, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL), MP_QSTR_pin); } - keypad_keys_obj_t *self = mp_obj_malloc(keypad_keys_obj_t, &keypad_keys_type); + keypad_keys_obj_t *self = mp_obj_malloc_with_finaliser(keypad_keys_obj_t, &keypad_keys_type); common_hal_keypad_keys_construct(self, num_pins, pins_array, value_when_pressed, args[ARG_pull].u_bool, interval, max_events, debounce_threshold); return MP_OBJ_FROM_PTR(self); @@ -170,6 +170,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(keypad_keys_deinit_obj, keypad_keys_deinit); //| static const mp_rom_map_elem_t keypad_keys_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&keypad_keys_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&keypad_keys_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/keypad/ShiftRegisterKeys.c b/shared-bindings/keypad/ShiftRegisterKeys.c index 7f89a11ecf1..fa3b39ab63b 100644 --- a/shared-bindings/keypad/ShiftRegisterKeys.c +++ b/shared-bindings/keypad/ShiftRegisterKeys.c @@ -159,7 +159,7 @@ static mp_obj_t keypad_shiftregisterkeys_make_new(const mp_obj_type_t *type, siz const uint8_t debounce_threshold = (uint8_t)mp_arg_validate_int_range(args[ARG_debounce_threshold].u_int, 1, 127, MP_QSTR_debounce_threshold); keypad_shiftregisterkeys_obj_t *self = - mp_obj_malloc(keypad_shiftregisterkeys_obj_t, &keypad_shiftregisterkeys_type); + mp_obj_malloc_with_finaliser(keypad_shiftregisterkeys_obj_t, &keypad_shiftregisterkeys_type); common_hal_keypad_shiftregisterkeys_construct( self, clock, num_data_pins, data_pins_array, latch, value_to_latch, num_key_counts, key_count_array, value_when_pressed, interval, max_events, debounce_threshold); @@ -219,6 +219,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(keypad_shiftregisterkeys_deinit_obj, keypad_shiftregis static const mp_rom_map_elem_t keypad_shiftregisterkeys_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&keypad_shiftregisterkeys_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&keypad_shiftregisterkeys_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/keypad_demux/DemuxKeyMatrix.c b/shared-bindings/keypad_demux/DemuxKeyMatrix.c index 554e461c228..9d25ddc44cf 100644 --- a/shared-bindings/keypad_demux/DemuxKeyMatrix.c +++ b/shared-bindings/keypad_demux/DemuxKeyMatrix.c @@ -122,7 +122,7 @@ static mp_obj_t keypad_demux_demuxkeymatrix_make_new(const mp_obj_type_t *type, column_pins_array[column] = pin; } - keypad_demux_demuxkeymatrix_obj_t *self = mp_obj_malloc(keypad_demux_demuxkeymatrix_obj_t, &keypad_demux_demuxkeymatrix_type); + keypad_demux_demuxkeymatrix_obj_t *self = mp_obj_malloc_with_finaliser(keypad_demux_demuxkeymatrix_obj_t, &keypad_demux_demuxkeymatrix_type); // Last arg is use_gc_allocator, true during VM use. common_hal_keypad_demux_demuxkeymatrix_construct(self, num_row_addr_pins, row_addr_pins_array, num_column_pins, column_pins_array, args[ARG_columns_to_anodes].u_bool, args[ARG_transpose].u_bool, interval, max_events, debounce_threshold, true); @@ -191,7 +191,7 @@ static mp_obj_t keypad_demux_demuxkeymatrix_key_number_to_row_column(mp_obj_t se const mp_uint_t key_number = (mp_uint_t)mp_arg_validate_int_range( mp_obj_get_int(key_number_in), - 0, (mp_int_t)common_hal_keypad_generic_get_key_count(self), + 0, (mp_int_t)common_hal_keypad_generic_get_key_count(self) - 1, MP_QSTR_key_number); mp_uint_t row; @@ -217,10 +217,10 @@ static mp_obj_t keypad_demux_demuxkeymatrix_row_column_to_key_number(mp_obj_t se check_for_deinit(self); const mp_uint_t row = (mp_uint_t)mp_arg_validate_int_range( - mp_obj_get_int(row_in), 0, (mp_int_t)common_hal_keypad_demux_demuxkeymatrix_get_row_count(self), MP_QSTR_row); + mp_obj_get_int(row_in), 0, (mp_int_t)common_hal_keypad_demux_demuxkeymatrix_get_row_count(self) - 1, MP_QSTR_row); const mp_int_t column = (mp_uint_t)mp_arg_validate_int_range( - mp_obj_get_int(column_in), 0, (mp_int_t)common_hal_keypad_demux_demuxkeymatrix_get_column_count(self), MP_QSTR_column); + mp_obj_get_int(column_in), 0, (mp_int_t)common_hal_keypad_demux_demuxkeymatrix_get_column_count(self) - 1, MP_QSTR_column); return MP_OBJ_NEW_SMALL_INT( (mp_int_t)common_hal_keypad_demux_demuxkeymatrix_row_column_to_key_number(self, row, column)); @@ -235,6 +235,7 @@ MP_DEFINE_CONST_FUN_OBJ_3(keypad_demux_demuxkeymatrix_row_column_to_key_number_o static const mp_rom_map_elem_t keypad_demux_demuxkeymatrix_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&keypad_demux_demuxkeymatrix_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&keypad_demux_demuxkeymatrix_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/memorymonitor/AllocationSize.c b/shared-bindings/memorymonitor/AllocationSize.c index 3113d6ed191..6a647765eb0 100644 --- a/shared-bindings/memorymonitor/AllocationSize.c +++ b/shared-bindings/memorymonitor/AllocationSize.c @@ -43,7 +43,7 @@ //| static mp_obj_t memorymonitor_allocationsize_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *all_args, mp_map_t *kw_args) { memorymonitor_allocationsize_obj_t *self = - m_new_obj(memorymonitor_allocationsize_obj_t, &memorymonitor_allocationsize_type); + mp_obj_malloc(memorymonitor_allocationsize_obj_t, &memorymonitor_allocationsize_type); common_hal_memorymonitor_allocationsize_construct(self); diff --git a/shared-bindings/microcontroller/Pin.c b/shared-bindings/microcontroller/Pin.c index 1fcde50e060..1fa2366b5f3 100644 --- a/shared-bindings/microcontroller/Pin.c +++ b/shared-bindings/microcontroller/Pin.c @@ -167,14 +167,6 @@ void assert_pin_free(const mcu_pin_obj_t *pin) { } } -void validate_pins(qstr what, uint8_t *pin_nos, mp_int_t max_pins, mp_obj_t seq, uint8_t *count_out) { - const mcu_pin_obj_t *pins[max_pins]; - validate_list_is_free_pins(what, pins, max_pins, seq, count_out); - for (mp_int_t i = 0; i < *count_out; i++) { - pin_nos[i] = common_hal_mcu_pin_number(pins[i]); - } -} - MP_NORETURN void raise_ValueError_invalid_pin(void) { mp_arg_error_invalid(MP_QSTR_pin); } diff --git a/shared-bindings/microcontroller/Pin.h b/shared-bindings/microcontroller/Pin.h index 8ddca71bfbb..1fbc64a1f33 100644 --- a/shared-bindings/microcontroller/Pin.h +++ b/shared-bindings/microcontroller/Pin.h @@ -20,7 +20,6 @@ const mcu_pin_obj_t *validate_obj_is_free_pin_or_none(mp_obj_t obj, qstr arg_nam void validate_no_duplicate_pins(mp_obj_t seq, qstr arg_name); void validate_no_duplicate_pins_2(mp_obj_t seq1, mp_obj_t seq2, qstr arg_name1, qstr arg_name2); void validate_list_is_free_pins(qstr what, const mcu_pin_obj_t **pins_out, mp_int_t max_pins, mp_obj_t seq, uint8_t *count_out); -void validate_pins(qstr what, uint8_t *pin_nos, mp_int_t max_pins, mp_obj_t seq, uint8_t *count_out); MP_NORETURN void raise_ValueError_invalid_pin(void); MP_NORETURN void raise_ValueError_invalid_pins(void); MP_NORETURN void raise_ValueError_invalid_pin_name(qstr pin_name); @@ -28,11 +27,9 @@ MP_NORETURN void raise_ValueError_invalid_pin_name(qstr pin_name); void assert_pin_free(const mcu_pin_obj_t *pin); bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin); +#if CIRCUITPY_BULK_RESET void common_hal_never_reset_pin(const mcu_pin_obj_t *pin); -void common_hal_reset_pin(const mcu_pin_obj_t *pin); -uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin); -void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin); -void common_hal_mcu_pin_claim_number(uint8_t pin_no); +#endif void common_hal_mcu_pin_reset_number(uint8_t pin_no); void shared_bindings_microcontroller_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); diff --git a/shared-bindings/microcontroller/Processor.c b/shared-bindings/microcontroller/Processor.c index a3518ab754f..5ab4f1e7c41 100644 --- a/shared-bindings/microcontroller/Processor.c +++ b/shared-bindings/microcontroller/Processor.c @@ -17,6 +17,8 @@ #include "py/mperrno.h" #include "py/objtype.h" #include "py/objproperty.h" +#include "py/objstr.h" +#include "py/persistentcode.h" #include "py/runtime.h" @@ -44,6 +46,31 @@ //| ... //| +//| architecture: Optional[str] +//| """The native ``.mpy`` architecture this build can load, as a `str`. (read-only) +//| +//| This is the name ``mpy-cross`` takes for ``-march``, such as ``"armv6m"``, +//| ``"armv7emsp"``, ``"xtensawin"`` or ``"rv32imc"``. +//| +//| Is `None` if this build cannot load native code from ``.mpy`` files. +//| """ +#if MICROPY_PERSISTENT_CODE_LOAD_NATIVE +static const MP_DEFINE_STR_OBJ(mcu_processor_architecture_str_obj, MPY_FEATURE_ARCH_NAME); +#endif + +static mp_obj_t mcu_processor_get_architecture(mp_obj_t self) { + #if MICROPY_PERSISTENT_CODE_LOAD_NATIVE + return MP_OBJ_FROM_PTR(&mcu_processor_architecture_str_obj); + #else + return mp_const_none; + #endif +} + +MP_DEFINE_CONST_FUN_OBJ_1(mcu_processor_get_architecture_obj, mcu_processor_get_architecture); + +MP_PROPERTY_GETTER(mcu_processor_architecture_obj, + (mp_obj_t)&mcu_processor_get_architecture_obj); + //| frequency: int //| """The CPU operating frequency in Hertz. //| @@ -143,6 +170,7 @@ MP_PROPERTY_GETTER(mcu_processor_voltage_obj, (mp_obj_t)&mcu_processor_get_voltage_obj); static const mp_rom_map_elem_t mcu_processor_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_architecture), MP_ROM_PTR(&mcu_processor_architecture_obj) }, { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&mcu_processor_frequency_obj) }, { MP_ROM_QSTR(MP_QSTR_reset_reason), MP_ROM_PTR(&mcu_processor_reset_reason_obj) }, { MP_ROM_QSTR(MP_QSTR_temperature), MP_ROM_PTR(&mcu_processor_temperature_obj) }, diff --git a/shared-bindings/mipidsi/Bus.c b/shared-bindings/mipidsi/Bus.c index bac3cd3c75e..35f0d7b3a4d 100644 --- a/shared-bindings/mipidsi/Bus.c +++ b/shared-bindings/mipidsi/Bus.c @@ -72,6 +72,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mipidsi_bus_deinit_obj, mipidsi_bus_deinit); static const mp_rom_map_elem_t mipidsi_bus_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mipidsi_bus_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mipidsi_bus_deinit_obj) }, }; static MP_DEFINE_CONST_DICT(mipidsi_bus_locals_dict, mipidsi_bus_locals_dict_table); diff --git a/shared-bindings/mipidsi/Display.c b/shared-bindings/mipidsi/Display.c index 149d31e52a9..7f5d802df97 100644 --- a/shared-bindings/mipidsi/Display.c +++ b/shared-bindings/mipidsi/Display.c @@ -209,6 +209,7 @@ MP_PROPERTY_GETTER(mipidsi_display_color_depth_obj, static const mp_rom_map_elem_t mipidsi_display_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mipidsi_display_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mipidsi_display_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&mipidsi_display_width_obj) }, { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&mipidsi_display_height_obj) }, diff --git a/shared-bindings/onewireio/OneWire.c b/shared-bindings/onewireio/OneWire.c index 2f69424cc23..fd0135ef6e9 100644 --- a/shared-bindings/onewireio/OneWire.c +++ b/shared-bindings/onewireio/OneWire.c @@ -42,7 +42,7 @@ static mp_obj_t onewireio_onewire_make_new(const mp_obj_type_t *type, size_t n_a mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); const mcu_pin_obj_t *pin = validate_obj_is_free_pin(args[ARG_pin].u_obj, MP_QSTR_pin); - onewireio_onewire_obj_t *self = mp_obj_malloc(onewireio_onewire_obj_t, &onewireio_onewire_type); + onewireio_onewire_obj_t *self = mp_obj_malloc_with_finaliser(onewireio_onewire_obj_t, &onewireio_onewire_type); common_hal_onewireio_onewire_construct(self, pin); return MP_OBJ_FROM_PTR(self); @@ -125,6 +125,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(onewireio_onewire_write_bit_obj, onewireio_onewire_obj static const mp_rom_map_elem_t onewireio_onewire_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&onewireio_onewire_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&onewireio_onewire_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&onewireio_onewire_reset_obj) }, diff --git a/shared-bindings/picogame/Bitmap.c b/shared-bindings/picogame/Bitmap.c index 8c970a5a0c0..46a4443891a 100644 --- a/shared-bindings/picogame/Bitmap.c +++ b/shared-bindings/picogame/Bitmap.c @@ -150,7 +150,9 @@ static mp_obj_t picogame_bitmap_make_new(const mp_obj_type_t *type, size_t n_arg // palette length in entries (informational; blitter assumes indices < this - see blit contract). self->pal_entries = (uint16_t)((pal_len / 2) > 65535 ? 65535 : (pal_len / 2)); if (args[ARG_transparent].u_obj != mp_const_none) { - self->transparent = mp_obj_get_int(args[ARG_transparent].u_obj); + mp_int_t t = mp_obj_get_int(args[ARG_transparent].u_obj); + // PAL8 keys are indexes: narrow to 8 bits so every blit path agrees. + self->transparent = (format == PICOGAME_FMT_PAL8) ? (uint8_t)t : (uint16_t)t; self->has_transparent = true; } else { self->transparent = 0; diff --git a/shared-bindings/picogame/Canvas.c b/shared-bindings/picogame/Canvas.c index 675826936ff..fde65572832 100644 --- a/shared-bindings/picogame/Canvas.c +++ b/shared-bindings/picogame/Canvas.c @@ -8,7 +8,9 @@ #include "shared-module/picogame/pg_compat.h" #include "shared-bindings/picogame/Canvas.h" #include "shared-bindings/picogame/Bitmap.h" +#if CIRCUITPY_FONTIO #include "shared-bindings/fontio/BuiltinFont.h" +#endif #include "shared-module/picogame/Canvas.h" //| class Canvas: @@ -79,10 +81,9 @@ static mp_obj_t picogame_canvas_make_new(const mp_obj_type_t *type, size_t n_arg self->transparent = 0; self->has_transparent = false; } - uint16_t fill = self->has_transparent ? self->transparent : 0; - for (size_t i = 0; i < (size_t)w * h; i++) { - self->data[i] = fill; - } + // clear() marks the surface dirty and reads the accumulator, so reset it on both sides. + picogame_canvas_dirty_reset(self); + picogame_canvas_clear(self, self->has_transparent ? self->transparent : 0); picogame_canvas_dirty_reset(self); return MP_OBJ_FROM_PTR(self); } @@ -512,10 +513,12 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(canvas_frame3d_obj, 7, 7, canvas_fram //| is drawn; no memory is retained between calls. Only ASCII characters are //| supported. If ``bg`` is given the glyph background is filled with it, //| otherwise it is transparent. Inside a `StripDraw` callback the view is a -//| Canvas, so ``view.text(...)`` draws text directly into the frame.""" +//| Canvas, so ``view.text(...)`` draws text directly into the frame. +//| Raises :py:class:`NotImplementedError` in builds without `fontio`.""" //| ... //| static mp_obj_t canvas_text(size_t n, const mp_obj_t *a) { + #if CIRCUITPY_FONTIO const char *s = mp_obj_str_get_str(a[3]); mp_int_t fg = mp_obj_get_int(a[4]); const void *font = MP_OBJ_TO_PTR(mp_arg_validate_type(a[5], &fontio_builtinfont_type, MP_QSTR_font)); @@ -524,6 +527,9 @@ static mp_obj_t canvas_text(size_t n, const mp_obj_t *a) { picogame_canvas_text(cv_self(a[0]), mp_obj_get_int(a[1]), mp_obj_get_int(a[2]), s, (uint16_t)fg, bg, has_bg, font); return mp_const_none; + #else + mp_raise_NotImplementedError(MP_ERROR_TEXT("Operation or feature not supported")); + #endif } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(canvas_text_obj, 6, 7, canvas_text); diff --git a/shared-bindings/picogame/Tilemap.c b/shared-bindings/picogame/Tilemap.c index 955deef1b96..a0a307d2f37 100644 --- a/shared-bindings/picogame/Tilemap.c +++ b/shared-bindings/picogame/Tilemap.c @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: MIT +#include + #include "py/runtime.h" #include "shared-module/picogame/pg_compat.h" #include "shared-bindings/picogame/Tilemap.h" @@ -175,11 +177,9 @@ static mp_obj_t picogame_tilemap_fill(mp_obj_t self_in, mp_obj_t value_in) { picogame_tilemap_obj_t *self = MP_OBJ_TO_PTR(self_in); uint8_t v = mp_obj_get_int(value_in) & 0xff; size_t total = (size_t)self->map_w * self->map_h; - for (size_t i = 0; i < total; i++) { - self->map[i] = v; - if (self->orient) { - self->orient[i] = 0; // a plain fill clears any per-cell orientation - } + memset(self->map, v, total); + if (self->orient) { + memset(self->orient, 0, total); // a plain fill clears any per-cell orientation } int x1, y1, x2, y2; picogame_tilemap_extent(self, &x1, &y1, &x2, &y2); diff --git a/shared-bindings/picogame/__init__.c b/shared-bindings/picogame/__init__.c index 1812c3860fa..5ccd1df548b 100644 --- a/shared-bindings/picogame/__init__.c +++ b/shared-bindings/picogame/__init__.c @@ -502,27 +502,42 @@ static mp_obj_t picogame_fbm1d(size_t n_args, const mp_obj_t *pos, mp_map_t *kw) static MP_DEFINE_CONST_FUN_OBJ_KW(picogame_fbm1d_obj, 1, picogame_fbm1d); #endif // float fbm2d / fbm1d +// floor((a * dd) / 65536) with 32-bit multiplies. Exact for a <= 2^16, dd <= 2^24. +static inline int32_t pg_mulshr16(uint32_t a, uint32_t dd) { + return (int32_t)(a * (dd >> 16) + ((a * (dd & 0xFFFF)) >> 16)); +} + // ---- fixed-point (Q16.16 coords, Q0.16 values) noise: the CANONICAL value-noise impl, // exposed under the plain names value2d/value1d/fbm2d/fbm1d. The inner math is integer // (float only at the Python boundary); ~1.8x faster than the retired float path. ---- -static inline uint32_t pg_nhash_raw(int32_t x, int32_t y, int32_t seed) { - uint32_t h = (uint32_t)x * 374761393u + (uint32_t)y * 668265263u + (uint32_t)seed * 362437u; + +// Mix step of the hash. The four corners of a cell differ only by constants +// in the input, so the coordinate part is computed once per cell. +static inline uint32_t pg_nhash_mix(uint32_t h) { h = (h ^ (h >> 13)) * 1274126177u; return (h ^ (h >> 16)) & 0xFFFFu; // Q0.16 in [0,1) } static inline uint32_t pg_smooth16(uint32_t t) { // t,result Q0.16: t*t*(3-2t) - uint32_t t2 = (t * t) >> 16; - uint32_t e = (3u << 16) - 2u * t; - return (uint32_t)(((uint64_t)t2 * e) >> 16); + uint32_t t2 = (t * t) >> 16; // t <= 0xFFFF, so t*t fits uint32 + uint32_t e = (3u << 16) - 2u * t; // e <= 3<<16, i.e. under 2^18 + return (uint32_t)pg_mulshr16(t2, e); // t2 <= 2^16 and e <= 2^24: exact } static inline uint32_t pg_lerp16(uint32_t a, uint32_t b, uint32_t u) { - return (uint32_t)((int32_t)a + (int32_t)(((int64_t)((int32_t)b - (int32_t)a) * (int32_t)u) >> 16)); + // Unsigned product with a sign split; a negative delta must floor like the old shift. + int32_t d = (int32_t)b - (int32_t)a; + if (d >= 0) { + return a + (((uint32_t)d * u) >> 16); + } + return a - ((((uint32_t)(-d) * u) + 0xFFFFu) >> 16); } static uint32_t pg_value2d_fx(int32_t X, int32_t Y, int32_t seed) { // X,Y Q16.16 -> Q0.16 int32_t xi = X >> 16, yi = Y >> 16; uint32_t xf = (uint32_t)(X - (xi << 16)), yf = (uint32_t)(Y - (yi << 16)); - uint32_t a = pg_nhash_raw(xi, yi, seed), b = pg_nhash_raw(xi + 1, yi, seed); - uint32_t c = pg_nhash_raw(xi, yi + 1, seed), d = pg_nhash_raw(xi + 1, yi + 1, seed); + uint32_t base = (uint32_t)xi * 374761393u + (uint32_t)yi * 668265263u + (uint32_t)seed * 362437u; + uint32_t a = pg_nhash_mix(base); // (xi, yi) + uint32_t b = pg_nhash_mix(base + 374761393u); // (xi+1, yi) + uint32_t c = pg_nhash_mix(base + 668265263u); // (xi, yi+1) + uint32_t d = pg_nhash_mix(base + 374761393u + 668265263u); uint32_t u = pg_smooth16(xf), v = pg_smooth16(yf); return pg_lerp16(pg_lerp16(a, b, u), pg_lerp16(c, d, u), v); } @@ -589,6 +604,18 @@ static mp_obj_t picogame_fbm1d_fx(size_t n_args, const mp_obj_t *pos, mp_map_t * static MP_DEFINE_CONST_FUN_OBJ_KW(picogame_fbm1d_fx_obj, 1, picogame_fbm1d_fx); +#if !CIRCUITPY_PICOGAME_FPU +// ((int64_t)a * b) >> 16 with two 32-bit multiplies. Exact for |b| <= 65536. +static __attribute__((noinline)) int32_t pg_fmul_basis(int32_t a, int32_t b) { + uint32_t hi = (uint32_t)(a >> 16) * (uint32_t)b; // modulo 2^32 on purpose + uint32_t l = (uint32_t)(a & 0xFFFF); + if (b >= 0) { + return (int32_t)(hi + ((l * (uint32_t)b) >> 16)); + } + return (int32_t)(hi - ((l * (uint32_t)(-b) + 0xFFFFu) >> 16)); +} +#endif + //| def project( //| cam: ReadableBuffer, //| pts: ReadableBuffer, @@ -666,10 +693,13 @@ static mp_obj_t picogame_project(size_t n_args, const mp_obj_t *args) { // the near plane - host-measured 23-34 px warps on close fly-bys at a file-browser world scale // (walls visibly broke). Correctness first: Q16 keeps the worst error a few px at any cz >= near, // for coords up to +-32k units; still ~4-5x faster than the same math in Python on the M0+. + // FMULB: same result as FMUL for |b| <= 65536 (every basis component), without + // __aeabi_lmul. The products by k keep FMUL because k is not bounded. #define FMUL(a, b) ((int32_t)(((int64_t)(a) * (b)) >> 16)) + #define FMULB(a, b) pg_fmul_basis((a), (b)) for (int i = 0; i < n; i++) { int32_t X = pts[i * 3] - ex, Y = pts[i * 3 + 1] - ey, Z = pts[i * 3 + 2] - ez; - int32_t cz = FMUL(X, fx) + FMUL(Y, fy) + FMUL(Z, fz); + int32_t cz = FMULB(X, fx) + FMULB(Y, fy) + FMULB(Z, fz); if (cz < near) { osx[i] = -32768; osy[i] = -32768; @@ -679,12 +709,13 @@ static mp_obj_t picogame_project(size_t n_args, const mp_obj_t *args) { // an int64 divide on the M0+ (no HW divide) and the lost cz precision costs <0.02 px (host- // measured). Needs FOCAL < ~250 (focal<<8 in uint32) and near >= 1/256 (cz>>8 nonzero). int32_t k = (int32_t)(((uint32_t)focal << 8) / (uint32_t)(cz >> 8)); - int32_t rr = FMUL(X, rx) + FMUL(Z, rz); - int32_t uu = FMUL(X, ux) + FMUL(Y, uy) + FMUL(Z, uz); + int32_t rr = FMULB(X, rx) + FMULB(Z, rz); + int32_t uu = FMULB(X, ux) + FMULB(Y, uy) + FMULB(Z, uz); osx[i] = (int16_t)((cx0 + FMUL(rr, k)) >> 16); osy[i] = (int16_t)((cy0 - FMUL(uu, k)) >> 16); } #undef FMUL +#undef FMULB #endif return mp_const_none; } @@ -753,8 +784,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picogame_project_obj, 5, 5, picogame_ // trig and passes Q16 ray params). map: read-only bytes, mw*mh wall types (0 = empty). pos*, l*x/l*y // (leftRay, column 0), s*x/s*y (rayStep per column) are all 16.16. wcolors: uint16[(maxtype+1)*2] - // [t*2] near, [t*2+1] side colour. top/bot/col: uint16 write buffers (len>=ncols); dist: int32 write -// buffer (perpendicular distance, 16.16). The int64 divides/muls are ONLY the per-column setup -// (O(ncols)); the DDA step loop is pure 32-bit. Mirrors the Python float fallback closely. +// buffer (perpendicular distance, 16.16). All arithmetic is 32-bit. // Optional arg 17 (runs - ONE uint16 write buffer, len>=5*ncols, laid out as five ncols-long // planes [x0s | x1s | tops | bots | colors]): also emit the RLE-MERGED wall runs (adjacent equal // columns fused; x in PIXELS = column*stride) and return the run count. The planes feed @@ -762,11 +792,18 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picogame_project_obj, 5, 5, picogame_ // loop into the same C pass (measured 2-6.5 ms/frame of interpreted merge at stride=1 on RP2040). // Callers clamp the LAST run's x1 to the screen width (stride rounding can overshoot by = 1. +static inline uint32_t pg_recip32(uint32_t v) { + return (uint32_t)(0u - v) / v + 1u; +} + static mp_obj_t picogame_raycast(size_t n_args, const mp_obj_t *args) { mp_buffer_info_t mi, wi, ti, bi, ci, di; mp_get_buffer_raise(args[0], &mi, MP_BUFFER_READ); - int mw = mp_obj_get_int(args[1]); - int mh = mp_obj_get_int(args[2]); + // Non-negative: the DDA below bounds-checks with unsigned compares. + int mw = picogame_imax(mp_obj_get_int(args[1]), 0); + int mh = picogame_imax(mp_obj_get_int(args[2]), 0); int32_t posx = mp_obj_get_int(args[3]); // camera x, 16.16 int32_t posy = mp_obj_get_int(args[4]); int32_t rdx = mp_obj_get_int(args[5]); // leftRay x (column 0), 16.16 - accumulates per column @@ -805,6 +842,7 @@ static mp_obj_t picogame_raycast(size_t n_args, const mp_obj_t *args) { int half = sh >> 1; int imapx0 = posx >> 16; int imapy0 = posy >> 16; + int off0 = imapy0 * mw + imapx0; // every column starts from the same cell int32_t fracx = posx & 0xFFFF; // fractional part of pos, 16.16 int32_t fracy = posy & 0xFFFF; const int32_t DD_CAP = (int32_t)1 << 24; // cap deltaDist so a 64-step accumulation stays in int32 @@ -816,41 +854,47 @@ static mp_obj_t picogame_raycast(size_t n_args, const mp_obj_t *args) { int mapy = imapy0; int32_t ax = rdx < 0 ? -rdx : rdx; int32_t ay = rdy < 0 ? -rdy : rdy; - // deltaDist = |1/rayDir| in 16.16 = (1<<32)/|rayDir_q16| (int64; per-column setup, not per-step) - int64_t ddx64 = ax ? (((int64_t)1 << 32) / ax) : (int64_t)DD_CAP; - int64_t ddy64 = ay ? (((int64_t)1 << 32) / ay) : (int64_t)DD_CAP; - int32_t ddx = ddx64 > DD_CAP ? DD_CAP : (int32_t)ddx64; - int32_t ddy = ddy64 > DD_CAP ? DD_CAP : (int32_t)ddy64; + // deltaDist = 2^32 / |rayDir|, capped at DD_CAP. ax < 256 is exactly the capped + // range, so the test also guards against ax == 0. + int32_t ddx = (ax < 256) ? DD_CAP : (int32_t)pg_recip32((uint32_t)ax); + int32_t ddy = (ay < 256) ? DD_CAP : (int32_t)pg_recip32((uint32_t)ay); int stepx, stepy; int32_t sidex, sidey; - // sideDist to the first grid line = (fractional distance) * deltaDist, 16.16 (int64 mul, setup only) + // sideDist to the first grid line = (fractional distance) * deltaDist, 16.16 (setup only) if (rdx < 0) { stepx = -1; - sidex = (int32_t)(((int64_t)fracx * ddx) >> 16); + sidex = pg_mulshr16((uint32_t)fracx, (uint32_t)ddx); } else { stepx = 1; - sidex = (int32_t)(((int64_t)(65536 - fracx) * ddx) >> 16); + sidex = pg_mulshr16((uint32_t)(65536 - fracx), (uint32_t)ddx); } if (rdy < 0) { stepy = -1; - sidey = (int32_t)(((int64_t)fracy * ddy) >> 16); + sidey = pg_mulshr16((uint32_t)fracy, (uint32_t)ddy); } else { stepy = 1; - sidey = (int32_t)(((int64_t)(65536 - fracy) * ddy) >> 16); + sidey = pg_mulshr16((uint32_t)(65536 - fracy), (uint32_t)ddy); } int side = 0; int cell = 1; + // Linear map offset, stepped with the cell coordinates. + int off = off0; + int stepy_off = stepy * mw; for (int i = 0; i < 64; i++) { // DDA - pure 32-bit if (sidex < sidey) { sidex += ddx; mapx += stepx; + off += stepx; side = 0; } else { sidey += ddy; mapy += stepy; + off += stepy_off; side = 1; } - cell = (mapx >= 0 && mapx < mw && mapy >= 0 && mapy < mh) ? map[mapy * mw + mapx] : 1; + // Unsigned compares also reject negative coordinates. + cell = ((unsigned)mapx < (unsigned)mw && (unsigned)mapy < (unsigned)mh) + ? map[off] : 1; if (cell) { break; } diff --git a/shared-bindings/ps2io/Ps2.c b/shared-bindings/ps2io/Ps2.c index 90e6b0b6045..eb61ef07f11 100644 --- a/shared-bindings/ps2io/Ps2.c +++ b/shared-bindings/ps2io/Ps2.c @@ -57,7 +57,7 @@ static mp_obj_t ps2io_ps2_make_new(const mp_obj_type_t *type, size_t n_args, siz const mcu_pin_obj_t *clock_pin = validate_obj_is_free_pin(args[ARG_clock_pin].u_obj, MP_QSTR_clock_pin); const mcu_pin_obj_t *data_pin = validate_obj_is_free_pin(args[ARG_data_pin].u_obj, MP_QSTR_data_pin); - ps2io_ps2_obj_t *self = mp_obj_malloc(ps2io_ps2_obj_t, &ps2io_ps2_type); + ps2io_ps2_obj_t *self = mp_obj_malloc_with_finaliser(ps2io_ps2_obj_t, &ps2io_ps2_type); common_hal_ps2io_ps2_construct(self, data_pin, clock_pin); return MP_OBJ_FROM_PTR(self); @@ -197,6 +197,7 @@ static mp_obj_t ps2_unary_op(mp_unary_op_t op, mp_obj_t self_in) { static const mp_rom_map_elem_t ps2io_ps2_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&ps2io_ps2_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ps2io_ps2_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_popleft), MP_ROM_PTR(&ps2io_ps2_popleft_obj) }, diff --git a/shared-bindings/pwmio/PWMOut.c b/shared-bindings/pwmio/PWMOut.c index 9619b4b9d6c..7cfb0d8a13f 100644 --- a/shared-bindings/pwmio/PWMOut.c +++ b/shared-bindings/pwmio/PWMOut.c @@ -139,7 +139,8 @@ static mp_obj_t pwmio_pwmout_make_new(const mp_obj_type_t *type, size_t n_args, const mcu_pin_obj_t *pin = validate_obj_is_free_pin(parsed_args[ARG_pin].u_obj, MP_QSTR_pin); - uint16_t duty_cycle = parsed_args[ARG_duty_cycle].u_int; + uint16_t duty_cycle = (uint16_t)mp_arg_validate_int_range( + parsed_args[ARG_duty_cycle].u_int, 0, 0xffff, MP_QSTR_duty_cycle); uint32_t frequency = parsed_args[ARG_frequency].u_int; bool variable_frequency = parsed_args[ARG_variable_frequency].u_bool; diff --git a/shared-bindings/pwmio/PWMOut.h b/shared-bindings/pwmio/PWMOut.h index 3c3093ae628..9524408b7c5 100644 --- a/shared-bindings/pwmio/PWMOut.h +++ b/shared-bindings/pwmio/PWMOut.h @@ -34,7 +34,9 @@ extern bool common_hal_pwmio_pwmout_get_variable_frequency(pwmio_pwmout_obj_t *s extern const mcu_pin_obj_t *common_hal_pwmio_pwmout_get_pin(pwmio_pwmout_obj_t *self); // This is used by the supervisor to claim PWMOut devices indefinitely. +#if CIRCUITPY_BULK_RESET extern void common_hal_pwmio_pwmout_never_reset(pwmio_pwmout_obj_t *self); +#endif extern void common_hal_pwmio_pwmout_reset_ok(pwmio_pwmout_obj_t *self); extern void common_hal_pwmio_pwmout_raise_error(pwmout_result_t result); diff --git a/shared-bindings/random/__init__.c b/shared-bindings/random/__init__.c index 81201e6f8ab..d3bcd29c9d9 100644 --- a/shared-bindings/random/__init__.c +++ b/shared-bindings/random/__init__.c @@ -116,7 +116,7 @@ static mp_obj_t random_randint(mp_obj_t a_in, mp_obj_t b_in) { if (a > b) { mp_raise_ValueError(NULL); } - return mp_obj_new_int(shared_modules_random_randrange(a, b + 1, 1)); + return mp_obj_new_int(shared_modules_random_randint(a, b)); } static MP_DEFINE_CONST_FUN_OBJ_2(random_randint_obj, random_randint); diff --git a/shared-bindings/random/__init__.h b/shared-bindings/random/__init__.h index 27f255d5d8b..288805b3706 100644 --- a/shared-bindings/random/__init__.h +++ b/shared-bindings/random/__init__.h @@ -13,5 +13,7 @@ void shared_modules_random_seed(mp_uint_t seed); mp_uint_t shared_modules_random_getrandbits(uint8_t n); mp_int_t shared_modules_random_randrange(mp_int_t start, mp_int_t stop, mp_int_t step); +// Returns a random integer in [a, b] inclusive. The caller must ensure a <= b. +mp_int_t shared_modules_random_randint(mp_int_t a, mp_int_t b); mp_float_t shared_modules_random_random(void); mp_float_t shared_modules_random_uniform(mp_float_t a, mp_float_t b); diff --git a/shared-bindings/rgbmatrix/RGBMatrix.c b/shared-bindings/rgbmatrix/RGBMatrix.c index 6242f981fdf..29b4a5eac38 100644 --- a/shared-bindings/rgbmatrix/RGBMatrix.c +++ b/shared-bindings/rgbmatrix/RGBMatrix.c @@ -11,6 +11,7 @@ #include "common-hal/rgbmatrix/RGBMatrix.h" #include "shared-bindings/rgbmatrix/RGBMatrix.h" +#include "shared-bindings/rgbmatrix/RowAddressMode.h" #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/microcontroller/__init__.h" #include "shared-bindings/util.h" @@ -21,101 +22,6 @@ //| class RGBMatrix: //| """Displays an in-memory framebuffer to a HUB75-style RGB LED matrix.""" //| - -extern Protomatter_core *_PM_protoPtr; - -static uint8_t validate_pin(mp_obj_t obj, qstr arg_name) { - const mcu_pin_obj_t *result = validate_obj_is_free_pin(obj, arg_name); - return common_hal_mcu_pin_number(result); -} - -static void claim_and_never_reset_pin(mp_obj_t pin) { - common_hal_mcu_pin_claim(pin); - common_hal_never_reset_pin(pin); -} - -static void claim_and_never_reset_pins(mp_obj_t seq) { - mp_int_t len = MP_OBJ_SMALL_INT_VALUE(mp_obj_len(seq)); - for (mp_int_t i = 0; i < len; i++) { - claim_and_never_reset_pin(mp_obj_subscr(seq, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL)); - } -} - -static void preflight_pins_or_throw(uint8_t clock_pin, uint8_t *rgb_pins, uint8_t rgb_pin_count, bool allow_inefficient) { - if (rgb_pin_count <= 0 || rgb_pin_count % 6 != 0 || rgb_pin_count > 30) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("The length of rgb_pins must be 6, 12, 18, 24, or 30")); - } - -// Most ports have a strict requirement for how the rgbmatrix pins are laid -// out; these two micros don't. Special-case it here. - #if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2) - uint32_t port = clock_pin / 32; - uint32_t bit_mask = 1 << (clock_pin % 32); - - for (uint8_t i = 0; i < rgb_pin_count; i++) { - uint32_t pin_port = rgb_pins[i] / 32; - - if (pin_port != port) { - mp_raise_ValueError_varg( - MP_ERROR_TEXT("rgb_pins[%d] is not on the same port as clock"), i); - } - - uint32_t pin_mask = 1 << (rgb_pins[i] % 32); - if (pin_mask & bit_mask) { - mp_raise_ValueError_varg( - MP_ERROR_TEXT("rgb_pins[%d] duplicates another pin assignment"), i); - } - - bit_mask |= pin_mask; - } - - if (allow_inefficient) { - return; - } - - uint8_t byte_mask = 0; - if (bit_mask & 0x000000FF) { - byte_mask |= 0b0001; - } - if (bit_mask & 0x0000FF00) { - byte_mask |= 0b0010; - } - if (bit_mask & 0x00FF0000) { - byte_mask |= 0b0100; - } - if (bit_mask & 0xFF000000) { - byte_mask |= 0b1000; - } - - uint8_t bytes_per_element = 0xff; - uint8_t ideal_bytes_per_element = (rgb_pin_count + 7) / 8; - - switch (byte_mask) { - case 0b0001: - case 0b0010: - case 0b0100: - case 0b1000: - bytes_per_element = 1; - break; - - case 0b0011: - case 0b1100: - bytes_per_element = 2; - break; - - default: - bytes_per_element = 4; - break; - } - - if (bytes_per_element != ideal_bytes_per_element) { - mp_raise_ValueError_varg( - MP_ERROR_TEXT("Pinout uses %d bytes per element, which consumes more than the ideal %d bytes. If this cannot be avoided, pass allow_inefficient=True to the constructor"), - bytes_per_element, ideal_bytes_per_element); - } - #endif -} - //| def __init__( //| self, //| *, @@ -131,13 +37,19 @@ static void preflight_pins_or_throw(uint8_t clock_pin, uint8_t *rgb_pins, uint8_ //| height: int = 0, //| tile: int = 1, //| serpentine: bool = True, +//| row_address_mode: RowAddressMode = RowAddressMode.BINARY, //| ) -> None: //| """Create a RGBMatrix object with the given attributes. The height of //| the display is determined by the number of rgb and address pins and the number of tiles: //| ``len(rgb_pins) // 3 * 2 ** len(address_pins) * abs(tile)``. With 6 RGB pins, 4 //| address lines, and a single matrix, the display will be 32 pixels tall. If the optional height //| parameter is specified and is not 0, it is checked against the calculated -//| height. +//| height. With ``row_address_mode=RowAddressMode.ABC``, ``height`` must be specified. +//| Each panel must have a power-of-two height, from 2 through 64 pixels. +//| Supply exactly three ``addr_pins`` in A, B, C order: A is the row +//| clock, B enables shifting, and C is serial row-selection data. +//| This protocol is separate from the panel's RGB pixel driver; an +//| FM6126A chip or HUB75E connector label does not identify the row mode. //| //| Tiled matrices, those with more than one panel, must be laid out `in a specific order, as detailed in the guide //| `_. @@ -191,13 +103,14 @@ static void preflight_pins_or_throw(uint8_t clock_pin, uint8_t *rgb_pins, uint8_ //| :param digitalio.DigitalInOut output_enable_pin: The matrix's output enable pin //| :param bool doublebuffer: True if the output is double-buffered //| :param Optional[WriteableBuffer] framebuffer: A pre-allocated framebuffer to use. If unspecified, a framebuffer is allocated -//| :param int height: The optional overall height of the whole matrix in pixels. This value is not required because it can be calculated as described above. +//| :param int height: The overall height of the whole matrix in pixels. Required for ABC addressing; otherwise optional and checked against the calculated height. +//| :param RowAddressMode row_address_mode: ``RowAddressMode.BINARY`` for parallel row addresses, or ``RowAddressMode.ABC`` for serial row selection using A, B and C. //| """ //| static mp_obj_t rgbmatrix_rgbmatrix_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_width, ARG_bit_depth, ARG_rgb_list, ARG_addr_list, - ARG_clock_pin, ARG_latch_pin, ARG_output_enable_pin, ARG_doublebuffer, ARG_framebuffer, ARG_height, ARG_tile, ARG_serpentine }; + ARG_clock_pin, ARG_latch_pin, ARG_output_enable_pin, ARG_doublebuffer, ARG_framebuffer, ARG_height, ARG_tile, ARG_serpentine, ARG_row_address_mode }; static const mp_arg_t allowed_args[] = { { MP_QSTR_width, MP_ARG_INT | MP_ARG_REQUIRED | MP_ARG_KW_ONLY }, { MP_QSTR_bit_depth, MP_ARG_INT | MP_ARG_REQUIRED | MP_ARG_KW_ONLY }, @@ -211,23 +124,23 @@ static mp_obj_t rgbmatrix_rgbmatrix_make_new(const mp_obj_type_t *type, size_t n { MP_QSTR_height, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 0 } }, { MP_QSTR_tile, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 1 } }, { MP_QSTR_serpentine, MP_ARG_BOOL | MP_ARG_KW_ONLY, { .u_bool = true } }, + { MP_QSTR_row_address_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, { .u_obj = (void *)&rgbmatrix_row_address_mode_BINARY_obj } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - rgbmatrix_rgbmatrix_obj_t *self = &allocate_display_bus_or_raise()->rgbmatrix; - self->base.type = &rgbmatrix_RGBMatrix_type; + rgbmatrix_rgbmatrix_obj_t *self; uint8_t rgb_count, addr_count; - uint8_t rgb_pins[MP_ARRAY_SIZE(self->rgb_pins)]; - uint8_t addr_pins[MP_ARRAY_SIZE(self->addr_pins)]; - uint8_t clock_pin = validate_pin(args[ARG_clock_pin].u_obj, MP_QSTR_clock_pin); - uint8_t latch_pin = validate_pin(args[ARG_latch_pin].u_obj, MP_QSTR_latch_pin); - uint8_t output_enable_pin = validate_pin(args[ARG_output_enable_pin].u_obj, MP_QSTR_output_enable_pin); + const mcu_pin_obj_t *rgb_pins[MP_ARRAY_SIZE(self->rgb_pins)]; + const mcu_pin_obj_t *addr_pins[MP_ARRAY_SIZE(self->addr_pins)]; + const mcu_pin_obj_t *clock_pin = validate_obj_is_free_pin(args[ARG_clock_pin].u_obj, MP_QSTR_clock_pin); + const mcu_pin_obj_t *latch_pin = validate_obj_is_free_pin(args[ARG_latch_pin].u_obj, MP_QSTR_latch_pin); + const mcu_pin_obj_t *output_enable_pin = validate_obj_is_free_pin(args[ARG_output_enable_pin].u_obj, MP_QSTR_output_enable_pin); mp_int_t bit_depth = mp_arg_validate_int_range(args[ARG_bit_depth].u_int, 1, 6, MP_QSTR_bit_depth); - validate_pins(MP_QSTR_rgb_pins, rgb_pins, MP_ARRAY_SIZE(self->rgb_pins), args[ARG_rgb_list].u_obj, &rgb_count); - validate_pins(MP_QSTR_addr_pins, addr_pins, MP_ARRAY_SIZE(self->addr_pins), args[ARG_addr_list].u_obj, &addr_count); + validate_list_is_free_pins(MP_QSTR_rgb_pins, rgb_pins, MP_ARRAY_SIZE(rgb_pins), args[ARG_rgb_list].u_obj, &rgb_count); + validate_list_is_free_pins(MP_QSTR_addr_pins, addr_pins, MP_ARRAY_SIZE(addr_pins), args[ARG_addr_list].u_obj, &addr_count); if (rgb_count % 6) { mp_raise_ValueError_varg(MP_ERROR_TEXT("Must use a multiple of 6 rgb pins, not %d"), rgb_count); @@ -235,7 +148,27 @@ static mp_obj_t rgbmatrix_rgbmatrix_make_new(const mp_obj_type_t *type, size_t n int tile = mp_arg_validate_int_min(args[ARG_tile].u_int, 1, MP_QSTR_tile); - int computed_height = (rgb_count / 3) * (1 << (addr_count)) * tile; + ProtomatterRowAddressMode row_address_mode = + (ProtomatterRowAddressMode)cp_enum_value(&rgbmatrix_row_address_mode_type, + args[ARG_row_address_mode].u_obj, MP_QSTR_row_address_mode); + uint8_t row_addr_count = addr_count; + if (row_address_mode == PROTOMATTER_ROW_ADDRESS_ABC) { + mp_arg_validate_length(addr_count, 3, MP_QSTR_addr_pins); + mp_int_t height = mp_arg_validate_int_min(args[ARG_height].u_int, 1, MP_QSTR_height); + // ABC has three physical pins regardless of the number of row pairs. + // Match the explicit overall height, including parallel chains and tiles. + for (row_addr_count = 0; row_addr_count <= 5; row_addr_count++) { + int rows = (rgb_count / 3) * (1 << row_addr_count); + if (rows && height % rows == 0 && height / rows == tile) { + break; + } + } + if (row_addr_count > 5) { + mp_arg_error_invalid(MP_QSTR_height); + } + } + + int computed_height = (rgb_count / 3) * (1 << row_addr_count) * tile; if (args[ARG_height].u_int != 0) { if (computed_height != args[ARG_height].u_int) { mp_raise_ValueError_varg( @@ -245,7 +178,9 @@ static mp_obj_t rgbmatrix_rgbmatrix_make_new(const mp_obj_type_t *type, size_t n mp_int_t width = mp_arg_validate_int_min(args[ARG_width].u_int, 1, MP_QSTR_width); - preflight_pins_or_throw(clock_pin, rgb_pins, rgb_count, true); + // Reserve a display bus only after validating constructor arguments. + self = &allocate_display_bus_or_raise()->rgbmatrix; + self->base.type = &rgbmatrix_RGBMatrix_type; common_hal_rgbmatrix_rgbmatrix_construct(self, width, @@ -254,13 +189,8 @@ static mp_obj_t rgbmatrix_rgbmatrix_make_new(const mp_obj_type_t *type, size_t n addr_count, addr_pins, clock_pin, latch_pin, output_enable_pin, args[ARG_doublebuffer].u_bool, - args[ARG_framebuffer].u_obj, tile, args[ARG_serpentine].u_bool, NULL); - - claim_and_never_reset_pins(args[ARG_rgb_list].u_obj); - claim_and_never_reset_pins(args[ARG_addr_list].u_obj); - claim_and_never_reset_pin(args[ARG_clock_pin].u_obj); - claim_and_never_reset_pin(args[ARG_output_enable_pin].u_obj); - claim_and_never_reset_pin(args[ARG_latch_pin].u_obj); + args[ARG_framebuffer].u_obj, tile, args[ARG_serpentine].u_bool, NULL, + row_addr_count, row_address_mode); return MP_OBJ_FROM_PTR(self); } @@ -353,6 +283,7 @@ MP_PROPERTY_GETTER(rgbmatrix_rgbmatrix_height_obj, static const mp_rom_map_elem_t rgbmatrix_rgbmatrix_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&rgbmatrix_rgbmatrix_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&rgbmatrix_rgbmatrix_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_brightness), MP_ROM_PTR(&rgbmatrix_rgbmatrix_brightness_obj) }, { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&rgbmatrix_rgbmatrix_refresh_obj) }, { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&rgbmatrix_rgbmatrix_width_obj) }, diff --git a/shared-bindings/rgbmatrix/RGBMatrix.h b/shared-bindings/rgbmatrix/RGBMatrix.h index 960abc66a3f..d245f20c32d 100644 --- a/shared-bindings/rgbmatrix/RGBMatrix.h +++ b/shared-bindings/rgbmatrix/RGBMatrix.h @@ -11,7 +11,7 @@ extern const mp_obj_type_t rgbmatrix_RGBMatrix_type; -void common_hal_rgbmatrix_rgbmatrix_construct(rgbmatrix_rgbmatrix_obj_t *self, int width, int bit_depth, uint8_t rgb_count, uint8_t *rgb_pins, uint8_t addr_count, uint8_t *addr_pins, uint8_t clock_pin, uint8_t latch_pin, uint8_t oe_pin, bool doublebuffer, mp_obj_t framebuffer, int8_t tile, bool serpentine, void *timer); +void common_hal_rgbmatrix_rgbmatrix_construct(rgbmatrix_rgbmatrix_obj_t *self, int width, int bit_depth, uint8_t rgb_count, const mcu_pin_obj_t **rgb_pins, uint8_t addr_count, const mcu_pin_obj_t **addr_pins, const mcu_pin_obj_t *clock_pin, const mcu_pin_obj_t *latch_pin, const mcu_pin_obj_t *oe_pin, bool doublebuffer, mp_obj_t framebuffer, int8_t tile, bool serpentine, void *timer, uint8_t row_addr_count, ProtomatterRowAddressMode row_address_mode); void common_hal_rgbmatrix_rgbmatrix_deinit(rgbmatrix_rgbmatrix_obj_t *); void rgbmatrix_rgbmatrix_collect_ptrs(rgbmatrix_rgbmatrix_obj_t *); void common_hal_rgbmatrix_rgbmatrix_get_bufinfo(rgbmatrix_rgbmatrix_obj_t *self, mp_buffer_info_t *bufinfo); diff --git a/shared-bindings/rgbmatrix/RowAddressMode.c b/shared-bindings/rgbmatrix/RowAddressMode.c new file mode 100644 index 00000000000..f9c4d3abdbf --- /dev/null +++ b/shared-bindings/rgbmatrix/RowAddressMode.c @@ -0,0 +1,33 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "py/enum.h" +#include "py/obj.h" + +#include "lib/protomatter/src/core.h" + +#include "shared-bindings/rgbmatrix/RowAddressMode.h" + +MAKE_ENUM_VALUE(rgbmatrix_row_address_mode_type, rgbmatrix_row_address_mode, BINARY, PROTOMATTER_ROW_ADDRESS_BINARY); +MAKE_ENUM_VALUE(rgbmatrix_row_address_mode_type, rgbmatrix_row_address_mode, ABC, PROTOMATTER_ROW_ADDRESS_ABC); + +//| class RowAddressMode: +//| """The protocol used to select rows on the matrix.""" +//| +//| BINARY: RowAddressMode +//| """Use parallel binary row addresses.""" +//| +//| ABC: RowAddressMode +//| """Use serial A, B, and C row selection.""" +//| +MAKE_ENUM_MAP(rgbmatrix_row_address_mode) { + MAKE_ENUM_MAP_ENTRY(rgbmatrix_row_address_mode, BINARY), + MAKE_ENUM_MAP_ENTRY(rgbmatrix_row_address_mode, ABC), +}; +static MP_DEFINE_CONST_DICT(rgbmatrix_row_address_mode_locals_dict, rgbmatrix_row_address_mode_locals_table); + +MAKE_PRINTER(rgbmatrix, rgbmatrix_row_address_mode); +MAKE_ENUM_TYPE(rgbmatrix, RowAddressMode, rgbmatrix_row_address_mode); diff --git a/shared-bindings/rgbmatrix/RowAddressMode.h b/shared-bindings/rgbmatrix/RowAddressMode.h new file mode 100644 index 00000000000..d76c9e4aaa7 --- /dev/null +++ b/shared-bindings/rgbmatrix/RowAddressMode.h @@ -0,0 +1,14 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/enum.h" +#include "py/obj.h" + +extern const mp_obj_type_t rgbmatrix_row_address_mode_type; +extern const cp_enum_obj_t rgbmatrix_row_address_mode_BINARY_obj; +extern const cp_enum_obj_t rgbmatrix_row_address_mode_ABC_obj; diff --git a/shared-bindings/rgbmatrix/__init__.c b/shared-bindings/rgbmatrix/__init__.c index f6e0c43fb93..f610967dfd7 100644 --- a/shared-bindings/rgbmatrix/__init__.c +++ b/shared-bindings/rgbmatrix/__init__.c @@ -10,6 +10,7 @@ #include "py/runtime.h" #include "shared-bindings/rgbmatrix/RGBMatrix.h" +#include "shared-bindings/rgbmatrix/RowAddressMode.h" //| """Low-level routines for bitbanged LED matrices //| @@ -20,6 +21,7 @@ static const mp_rom_map_elem_t rgbmatrix_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_rgbmatrix) }, { MP_ROM_QSTR(MP_QSTR_RGBMatrix), MP_ROM_PTR(&rgbmatrix_RGBMatrix_type) }, + { MP_ROM_QSTR(MP_QSTR_RowAddressMode), MP_ROM_PTR(&rgbmatrix_row_address_mode_type) }, }; static MP_DEFINE_CONST_DICT(rgbmatrix_module_globals, rgbmatrix_module_globals_table); diff --git a/shared-bindings/sdioio/SDCard.c b/shared-bindings/sdioio/SDCard.c index baf1e1660e8..4466bc5a290 100644 --- a/shared-bindings/sdioio/SDCard.c +++ b/shared-bindings/sdioio/SDCard.c @@ -85,7 +85,7 @@ static mp_obj_t sdioio_sdcard_make_new(const mp_obj_type_t *type, size_t n_args, uint8_t num_data; validate_list_is_free_pins(MP_QSTR_data, data_pins, MP_ARRAY_SIZE(data_pins), args[ARG_data].u_obj, &num_data); - sdioio_sdcard_obj_t *self = mp_obj_malloc(sdioio_sdcard_obj_t, &sdioio_SDCard_type); + sdioio_sdcard_obj_t *self = mp_obj_malloc_with_finaliser(sdioio_sdcard_obj_t, &sdioio_SDCard_type); common_hal_sdioio_sdcard_construct(self, clock, command, num_data, data_pins, args[ARG_frequency].u_int); return MP_OBJ_FROM_PTR(self); } @@ -241,6 +241,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(sdioio_sdcard_deinit_obj, sdioio_sdcard_obj_deinit); static const mp_rom_map_elem_t sdioio_sdcard_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&sdioio_sdcard_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&sdioio_sdcard_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/sdioio/SDCard.h b/shared-bindings/sdioio/SDCard.h index 042521aea50..f20b164b72e 100644 --- a/shared-bindings/sdioio/SDCard.h +++ b/shared-bindings/sdioio/SDCard.h @@ -46,4 +46,6 @@ mp_negative_errno_t sdioio_sdcard_writeblocks(mp_obj_t self_in, uint8_t *buf, ui bool sdioio_sdcard_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, mp_int_t *out_value); // This is used by the supervisor to claim SDIO devices indefinitely. +#if CIRCUITPY_BULK_RESET extern void common_hal_sdioio_sdcard_never_reset(sdioio_sdcard_obj_t *self); +#endif diff --git a/shared-bindings/sharpdisplay/SharpMemoryFramebuffer.c b/shared-bindings/sharpdisplay/SharpMemoryFramebuffer.c index e1e2469e361..8f4cff36422 100644 --- a/shared-bindings/sharpdisplay/SharpMemoryFramebuffer.c +++ b/shared-bindings/sharpdisplay/SharpMemoryFramebuffer.c @@ -95,6 +95,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(sharpdisplay_framebuffer_deinit_obj, sharpdispl static const mp_rom_map_elem_t sharpdisplay_framebuffer_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&sharpdisplay_framebuffer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&sharpdisplay_framebuffer_deinit_obj) }, }; static MP_DEFINE_CONST_DICT(sharpdisplay_framebuffer_locals_dict, sharpdisplay_framebuffer_locals_dict_table); diff --git a/shared-bindings/spitarget/SPITarget.c b/shared-bindings/spitarget/SPITarget.c index 08f6d554f3a..61e6e90927e 100644 --- a/shared-bindings/spitarget/SPITarget.c +++ b/shared-bindings/spitarget/SPITarget.c @@ -48,7 +48,7 @@ static mp_obj_t spitarget_spi_target_make_new(const mp_obj_type_t *type, size_t const mcu_pin_obj_t *miso = validate_obj_is_free_pin(args[ARG_miso].u_obj, MP_QSTR_miso); const mcu_pin_obj_t *ss = validate_obj_is_free_pin(args[ARG_ss].u_obj, MP_QSTR_ss); - spitarget_spi_target_obj_t *self = mp_obj_malloc(spitarget_spi_target_obj_t, &spitarget_spi_target_type); + spitarget_spi_target_obj_t *self = mp_obj_malloc_with_finaliser(spitarget_spi_target_obj_t, &spitarget_spi_target_type); common_hal_spitarget_spi_target_construct(self, sck, mosi, miso, ss); return MP_OBJ_FROM_PTR(self); @@ -172,6 +172,7 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(spitarget_spi_target_wait_transfer_obj, 1, spi static const mp_rom_map_elem_t spitarget_spi_target_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&spitarget_spi_target_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&spitarget_spi_target_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&spitarget_spi_target___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_load_packet), MP_ROM_PTR(&spitarget_spi_target_load_packet_obj) }, diff --git a/shared-bindings/struct/__init__.c b/shared-bindings/struct/__init__.c index 3f9fd40895e..08f3aa3d307 100644 --- a/shared-bindings/struct/__init__.c +++ b/shared-bindings/struct/__init__.c @@ -24,7 +24,10 @@ //| Supported size/byte order prefixes: *@*, *<*, *>*, *!*. //| //| Supported format codes: *b*, *B*, *x*, *h*, *H*, *i*, *I*, *l*, *L*, *q*, *Q*, -//| *s*, *P*, *f*, *d* (the latter 2 depending on the floating-point support).""" +//| *s*, *P*, *f*, *d* (the latter 2 depending on the floating-point support). +//| +//| **Limitations**: *d* is not available on non-full builds, such as most SAMD21 boards. +//| Using it raises ``ValueError``.""" //| //| diff --git a/shared-bindings/synthio/MidiTrack.c b/shared-bindings/synthio/MidiTrack.c index 0304df12a81..89c50282bab 100644 --- a/shared-bindings/synthio/MidiTrack.c +++ b/shared-bindings/synthio/MidiTrack.c @@ -69,10 +69,10 @@ static mp_obj_t synthio_miditrack_make_new(const mp_obj_type_t *type, size_t n_a mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); - synthio_miditrack_obj_t *self = mp_obj_malloc(synthio_miditrack_obj_t, &synthio_miditrack_type); + synthio_miditrack_obj_t *self = mp_obj_malloc_with_finaliser(synthio_miditrack_obj_t, &synthio_miditrack_type); common_hal_synthio_miditrack_construct(self, (uint8_t *)bufinfo.buf, bufinfo.len, - args[ARG_tempo].u_int, + mp_arg_validate_int_min(args[ARG_tempo].u_int, 1, MP_QSTR_tempo), args[ARG_sample_rate].u_int, args[ARG_waveform].u_obj, mp_const_none, @@ -156,6 +156,7 @@ MP_PROPERTY_GETTER(synthio_miditrack_error_location_obj, static const mp_rom_map_elem_t synthio_miditrack_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&synthio_miditrack_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&synthio_miditrack_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/synthio/Synthesizer.c b/shared-bindings/synthio/Synthesizer.c index 35cc42a2037..41f941a1151 100644 --- a/shared-bindings/synthio/Synthesizer.c +++ b/shared-bindings/synthio/Synthesizer.c @@ -61,9 +61,9 @@ static mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - synthio_synthesizer_obj_t *self = mp_obj_malloc(synthio_synthesizer_obj_t, &synthio_synthesizer_type); + synthio_synthesizer_obj_t *self = mp_obj_malloc_with_finaliser(synthio_synthesizer_obj_t, &synthio_synthesizer_type); common_hal_synthio_synthesizer_construct(self, - args[ARG_sample_rate].u_int, + mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate), args[ARG_channel_count].u_int, args[ARG_waveform].u_obj, args[ARG_envelope].u_obj); @@ -288,6 +288,7 @@ static const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_release_then_press), MP_ROM_PTR(&synthio_synthesizer_change_obj) }, { MP_ROM_QSTR(MP_QSTR_release_all_then_press), MP_ROM_PTR(&synthio_synthesizer_release_all_then_press_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&synthio_synthesizer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&synthio_synthesizer_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, diff --git a/shared-bindings/synthio/__init__.c b/shared-bindings/synthio/__init__.c index 6a21834751b..3f9bc3f9274 100644 --- a/shared-bindings/synthio/__init__.c +++ b/shared-bindings/synthio/__init__.c @@ -12,8 +12,7 @@ #include "py/obj.h" #include "py/objnamedtuple.h" #include "py/runtime.h" -#include "extmod/vfs_fat.h" -#include "extmod/vfs_posix.h" +#include "py/stream.h" #include "shared-bindings/synthio/__init__.h" #include "shared-bindings/synthio/Biquad.h" @@ -211,16 +210,22 @@ static mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - if (!mp_obj_is_type(args[ARG_file].u_obj, &mp_type_vfs_fat_fileio)) { + mp_obj_t file_obj = args[ARG_file].u_obj; + // Any readable, seekable binary stream works, regardless of which + // filesystem it came from. + const mp_stream_p_t *stream_p = mp_get_stream_raise(file_obj, MP_STREAM_OP_READ | MP_STREAM_OP_IOCTL); + if (stream_p->is_text) { mp_raise_TypeError(MP_ERROR_TEXT("file must be a file opened in byte mode")); } - pyb_file_obj_t *file = MP_OBJ_TO_PTR(args[ARG_file].u_obj); uint8_t chunk_header[14]; - f_rewind(&file->fp); - UINT bytes_read; - if (f_read(&file->fp, chunk_header, sizeof(chunk_header), &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); + int errcode; + if (mp_stream_seek(file_obj, 0, MP_SEEK_SET, &errcode) == (mp_off_t)-1) { + mp_raise_OSError(errcode); + } + mp_uint_t bytes_read = mp_stream_rw(file_obj, chunk_header, sizeof(chunk_header), &errcode, MP_STREAM_RW_READ); + if (bytes_read == MP_STREAM_ERROR) { + mp_raise_OSError(errcode); } if (bytes_read != sizeof(chunk_header) || memcmp(chunk_header, "MThd\0\0\0\6\0\0\0\1", 12)) { @@ -235,8 +240,9 @@ static mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma tempo = 2 * ((chunk_header[12] << 8) | chunk_header[13]); } - if (f_read(&file->fp, chunk_header, 8, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); + bytes_read = mp_stream_rw(file_obj, chunk_header, 8, &errcode, MP_STREAM_RW_READ); + if (bytes_read == MP_STREAM_ERROR) { + mp_raise_OSError(errcode); } if (bytes_read != 8 || memcmp(chunk_header, "MTrk", 4)) { mp_arg_error_invalid(MP_QSTR_file); @@ -244,26 +250,21 @@ static mp_obj_t synthio_from_file(size_t n_args, const mp_obj_t *pos_args, mp_ma uint32_t track_size = (chunk_header[4] << 24) | (chunk_header[5] << 16) | (chunk_header[6] << 8) | chunk_header[7]; uint8_t *buffer = m_malloc_without_collect(track_size); - if (f_read(&file->fp, buffer, track_size, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); + bytes_read = mp_stream_rw(file_obj, buffer, track_size, &errcode, MP_STREAM_RW_READ); + if (bytes_read == MP_STREAM_ERROR) { + mp_raise_OSError(errcode); } if (bytes_read != track_size) { mp_arg_error_invalid(MP_QSTR_file); } - synthio_miditrack_obj_t *result = mp_obj_malloc(synthio_miditrack_obj_t, &synthio_miditrack_type); + synthio_miditrack_obj_t *result = mp_obj_malloc_with_finaliser(synthio_miditrack_obj_t, &synthio_miditrack_type); common_hal_synthio_miditrack_construct(result, buffer, track_size, tempo, args[ARG_sample_rate].u_int, args[ARG_waveform].u_obj, mp_const_none, args[ARG_envelope].u_obj ); - #if MICROPY_MALLOC_USES_ALLOCATED_SIZE - m_free(buffer, track_size); - #else - m_free(buffer); - #endif - return MP_OBJ_FROM_PTR(result); } MP_DEFINE_CONST_FUN_OBJ_KW(synthio_from_file_obj, 1, synthio_from_file); diff --git a/shared-bindings/touchio/TouchIn.c b/shared-bindings/touchio/TouchIn.c index 2250e88f599..513e52aa90d 100644 --- a/shared-bindings/touchio/TouchIn.c +++ b/shared-bindings/touchio/TouchIn.c @@ -53,7 +53,7 @@ static mp_obj_t touchio_touchin_make_new(const mp_obj_type_t *type, const mcu_pin_obj_t *pin = validate_obj_is_free_pin(args[ARG_pin].u_obj, MP_QSTR_pin); const digitalio_pull_t pull = validate_pull(args[ARG_pull].u_obj, MP_QSTR_pull); - touchio_touchin_obj_t *self = mp_obj_malloc(touchio_touchin_obj_t, &touchio_touchin_type); + touchio_touchin_obj_t *self = mp_obj_malloc_with_finaliser(touchio_touchin_obj_t, &touchio_touchin_type); common_hal_touchio_touchin_construct(self, pin, pull); return MP_OBJ_FROM_PTR(self); @@ -161,6 +161,7 @@ static const mp_rom_map_elem_t touchio_touchin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&touchio_touchin_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&touchio_touchin_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&touchio_touchin_value_obj)}, { MP_ROM_QSTR(MP_QSTR_raw_value), MP_ROM_PTR(&touchio_touchin_raw_value_obj)}, diff --git a/shared-bindings/traceback/__init__.c b/shared-bindings/traceback/__init__.c index 651ec2c0142..dcdf2914a4f 100644 --- a/shared-bindings/traceback/__init__.c +++ b/shared-bindings/traceback/__init__.c @@ -102,7 +102,18 @@ static void traceback_exception_common(bool is_print_exception, mp_print_t *prin exc->traceback = (mp_obj_traceback_t *)&mp_const_empty_traceback_obj; } - shared_module_traceback_print_exception(MP_OBJ_TO_PTR(value), print, limit); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + shared_module_traceback_print_exception(MP_OBJ_TO_PTR(value), print, limit); + nlr_pop(); + } else { + exc->traceback = trace_backup; + #if MICROPY_CPYTHON_EXCEPTION_CHAIN + exc->context = context_backup; + exc->cause = cause_backup; + #endif + nlr_jump(nlr.ret_val); + } exc->traceback = trace_backup; #if MICROPY_CPYTHON_EXCEPTION_CHAIN exc->context = context_backup; diff --git a/shared-bindings/usb/core/__init__.c b/shared-bindings/usb/core/__init__.c index 29dab285084..d272de119ca 100644 --- a/shared-bindings/usb/core/__init__.c +++ b/shared-bindings/usb/core/__init__.c @@ -89,7 +89,7 @@ static mp_obj_t _next_device(usb_core_devices_obj_t *iter) { // We passed the filters. Now make a properly allocated object to // return to the user. - usb_core_device_obj_t *self = mp_obj_malloc(usb_core_device_obj_t, &usb_core_device_type); + usb_core_device_obj_t *self = mp_obj_malloc_with_finaliser(usb_core_device_obj_t, &usb_core_device_type); common_hal_usb_core_device_construct(self, i); iter->next_index = i + 1; @@ -142,7 +142,7 @@ static mp_obj_t usb_core_find(size_t n_args, const mp_obj_t *pos_args, mp_map_t // Copy the temp iter contents to a heap object before we return it. // We could do this up front but GCC falsely detects that we may return // the stack copy. - usb_core_devices_obj_t *iter = m_new_obj(usb_core_devices_obj_t); + usb_core_devices_obj_t *iter = mp_obj_malloc_with_finaliser(usb_core_devices_obj_t, &usb_core_devices_type); memcpy(iter, &temp_iter, sizeof(usb_core_devices_obj_t)); return MP_OBJ_FROM_PTR(iter); } diff --git a/shared-bindings/usb_cdc/__init__.c b/shared-bindings/usb_cdc/__init__.c index 65d343b0351..b6ea1f354b7 100644 --- a/shared-bindings/usb_cdc/__init__.c +++ b/shared-bindings/usb_cdc/__init__.c @@ -89,6 +89,89 @@ static mp_obj_t usb_cdc_enable(size_t n_args, const mp_obj_t *pos_args, mp_map_t } MP_DEFINE_CONST_FUN_OBJ_KW(usb_cdc_enable_obj, 0, usb_cdc_enable); +#if CIRCUITPY_FULL_BUILD +char *custom_usb_cdc_console_control_interface_name = NULL; +char *custom_usb_cdc_console_data_interface_name = NULL; +char *custom_usb_cdc_data_control_interface_name = NULL; +char *custom_usb_cdc_data_data_interface_name = NULL; + +static void set_name(mp_obj_t name_obj, qstr arg_name_qstr, char **custom_name_p) { + if (name_obj != mp_const_none) { + mp_buffer_info_t name; + mp_get_buffer_raise(name_obj, &name, MP_BUFFER_READ); + mp_arg_validate_length_range(name.len, 1, 126, arg_name_qstr); + + if (*custom_name_p == NULL) { + *custom_name_p = port_malloc(sizeof(char) * 128, false); + } + + memcpy(*custom_name_p, name.buf, name.len); + (*custom_name_p)[name.len] = 0; + } +} + +//| def set_names( +//| *, +//| console_control_interface_name: Optional[str] = None, +//| console_data_interface_name: Optional[str] = None, +//| data_control_interface_name: Optional[str] = None, +//| data_data_interface_name: Optional[str] = None, +//| ) -> None: +//| """Override the CDC interface names in the USB Interface Descriptors. +//| +//| Each CDC serial device has a control interface and a data interface. +//| +//| :param Optional[str] console_control_interface_name: an ASCII string (or buffer) of at most 126 characters for the console control interface, or ``None`` to use the default name. +//| :param Optional[str] console_data_interface_name: an ASCII string (or buffer) of at most 126 characters for the console data interface, or ``None`` to use the default name. +//| :param Optional[str] data_control_interface_name: an ASCII string (or buffer) of at most 126 characters for the data-port control interface, or ``None`` to use the default name. +//| :param Optional[str] data_data_interface_name: an ASCII string (or buffer) of at most 126 characters for the data-port data interface, or ``None`` to use the default name. +//| +//| This method must be called in ``boot.py`` to have any effect. +//| +//| Not available on boards without native USB support or on reduced builds. +//| """ +//| ... +//| +//| +static mp_obj_t usb_cdc_set_names(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_console_control_interface_name, + ARG_console_data_interface_name, + ARG_data_control_interface_name, + ARG_data_data_interface_name + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_console_control_interface_name, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_console_data_interface_name, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_data_control_interface_name, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_data_data_interface_name, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args); + + set_name( + args[ARG_console_control_interface_name].u_obj, + MP_QSTR_console_control_interface_name, + &custom_usb_cdc_console_control_interface_name); + set_name( + args[ARG_console_data_interface_name].u_obj, + MP_QSTR_console_data_interface_name, + &custom_usb_cdc_console_data_interface_name); + set_name( + args[ARG_data_control_interface_name].u_obj, + MP_QSTR_data_control_interface_name, + &custom_usb_cdc_data_control_interface_name); + set_name( + args[ARG_data_data_interface_name].u_obj, + MP_QSTR_data_data_interface_name, + &custom_usb_cdc_data_data_interface_name); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(usb_cdc_set_names_obj, 0, usb_cdc_set_names); + +#endif + // The usb_cdc module dict is mutable so that .console and .data may // be set to a Serial or to None depending on whether they are enabled or not. static mp_map_elem_t usb_cdc_module_globals_table[] = { @@ -98,6 +181,9 @@ static mp_map_elem_t usb_cdc_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_data), mp_const_none }, { MP_ROM_QSTR(MP_QSTR_disable), MP_OBJ_FROM_PTR(&usb_cdc_disable_obj) }, { MP_ROM_QSTR(MP_QSTR_enable), MP_OBJ_FROM_PTR(&usb_cdc_enable_obj) }, + #if CIRCUITPY_FULL_BUILD + { MP_ROM_QSTR(MP_QSTR_set_names), MP_OBJ_FROM_PTR(&usb_cdc_set_names_obj) }, + #endif }; static MP_DEFINE_MUTABLE_DICT(usb_cdc_module_globals, usb_cdc_module_globals_table); diff --git a/shared-bindings/usb_cdc/__init__.h b/shared-bindings/usb_cdc/__init__.h index 154f259f410..d2665631c0c 100644 --- a/shared-bindings/usb_cdc/__init__.h +++ b/shared-bindings/usb_cdc/__init__.h @@ -12,6 +12,13 @@ #include "shared-module/usb_cdc/__init__.h" #endif +#if CIRCUITPY_FULL_BUILD +extern char *custom_usb_cdc_console_control_interface_name; +extern char *custom_usb_cdc_console_data_interface_name; +extern char *custom_usb_cdc_data_control_interface_name; +extern char *custom_usb_cdc_data_data_interface_name; +#endif + // Set the module dict entries. void usb_cdc_set_console(mp_obj_t serial_obj); void usb_cdc_set_data(mp_obj_t serial_obj); diff --git a/shared-bindings/wifi/Monitor.c b/shared-bindings/wifi/Monitor.c index fb09c4b4f77..55a786c5428 100644 --- a/shared-bindings/wifi/Monitor.c +++ b/shared-bindings/wifi/Monitor.c @@ -19,7 +19,11 @@ //| def __init__(self, channel: Optional[int] = 1, queue: Optional[int] = 128) -> None: //| """Initialize `wifi.Monitor` singleton. //| -//| :param int channel: The WiFi channel to scan. +//| :param int channel: The WiFi channel to scan. On dual-band radios, +//| 5 GHz channels (36 and above) may also be given. A channel the +//| radio does not support raises `ValueError`. While the station is +//| connected the radio stays on the station's channel, and +//| `channel` reports that channel. //| :param int queue: The queue size for buffering the packet. //| //| """ @@ -35,12 +39,14 @@ static mp_obj_t wifi_monitor_make_new(const mp_obj_type_t *type, size_t n_args, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_int_t channel = mp_arg_validate_int_range(args[ARG_channel].u_int, 1, 13, MP_QSTR_channel); + // 165 is the highest channel in the scan pattern. Channels the radio + // doesn't support are rejected by the port rather than here. + mp_int_t channel = mp_arg_validate_int_range(args[ARG_channel].u_int, 1, 165, MP_QSTR_channel); mp_int_t queue = mp_arg_validate_int_min(args[ARG_queue].u_int, 0, MP_QSTR_queue); wifi_monitor_obj_t *self = MP_STATE_VM(wifi_monitor_singleton); if (common_hal_wifi_monitor_deinited()) { - self = mp_obj_malloc(wifi_monitor_obj_t, &wifi_monitor_type); + self = mp_obj_malloc_with_finaliser(wifi_monitor_obj_t, &wifi_monitor_type); common_hal_wifi_monitor_construct(self, channel, queue); MP_STATE_VM(wifi_monitor_singleton) = self; } @@ -49,17 +55,16 @@ static mp_obj_t wifi_monitor_make_new(const mp_obj_type_t *type, size_t n_args, } //| channel: int -//| """The WiFi channel to scan.""" +//| """The WiFi channel to scan. A channel the radio does not support raises +//| `ValueError`. While the station is connected the radio stays on the +//| station's channel, and this reports that channel.""" static mp_obj_t wifi_monitor_obj_get_channel(mp_obj_t self_in) { return common_hal_wifi_monitor_get_channel(self_in); } MP_DEFINE_CONST_FUN_OBJ_1(wifi_monitor_get_channel_obj, wifi_monitor_obj_get_channel); static mp_obj_t wifi_monitor_obj_set_channel(mp_obj_t self_in, mp_obj_t channel) { - mp_int_t c = mp_obj_get_int(channel); - if (c < 1 || c > 13) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("%q out of bounds"), MP_QSTR_channel); - } + mp_int_t c = mp_arg_validate_int_range(mp_obj_get_int(channel), 1, 165, MP_QSTR_channel); common_hal_wifi_monitor_set_channel(self_in, c); return mp_const_none; } @@ -131,6 +136,7 @@ static const mp_rom_map_elem_t wifi_monitor_locals_dict_table[] = { // functions { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&wifi_monitor_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&wifi_monitor_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_lost), MP_ROM_PTR(&wifi_monitor_lost_obj) }, { MP_ROM_QSTR(MP_QSTR_queued), MP_ROM_PTR(&wifi_monitor_queued_obj) }, { MP_ROM_QSTR(MP_QSTR_packet), MP_ROM_PTR(&wifi_monitor_packet_obj) }, diff --git a/shared-bindings/wifi/Radio.c b/shared-bindings/wifi/Radio.c index 9dffaad995d..c9aafc903ba 100644 --- a/shared-bindings/wifi/Radio.c +++ b/shared-bindings/wifi/Radio.c @@ -342,6 +342,8 @@ MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_stop_station_obj, wifi_radio_stop_station); //| //| If ``channel`` is given, the access point will use that channel unless //| a station is already operating on a different channel. +//| On dual-band radios, 5 GHz channels (36 and above) may also be given. +//| A channel the radio does not support raises `ValueError`. //| //| If ``authmode`` is not None, the access point will use the given authentication modes. //| If a non-empty password is given, ``authmode`` must not include ``OPEN``. @@ -363,7 +365,8 @@ MP_DEFINE_CONST_FUN_OBJ_1(wifi_radio_stop_station_obj, wifi_radio_stop_station); //| //| .. note:: //| -//| In the raspberrypi port (RP2040 CYW43), ``max_connections`` is ignored. +//| In the raspberrypi port (RP2040 CYW43), ``max_connections`` is ignored +//| and only 2.4 GHz channels (1-13) are accepted. //| """ //| ... //| @@ -403,7 +406,9 @@ static mp_obj_t wifi_radio_start_ap(size_t n_args, const mp_obj_t *pos_args, mp_ } } - mp_int_t channel = mp_arg_validate_int_range(args[ARG_channel].u_int, 1, 13, MP_QSTR_channel); + // 165 is the highest channel in the scan pattern. Channels the radio + // doesn't support are rejected by the port rather than here. + mp_int_t channel = mp_arg_validate_int_range(args[ARG_channel].u_int, 1, 165, MP_QSTR_channel); if (authmode == AUTHMODE_OPEN && password.len > 0) { mp_raise_ValueError(MP_ERROR_TEXT("AuthMode.OPEN is not used with password")); diff --git a/shared-bindings/zlib/__init__.c b/shared-bindings/zlib/__init__.c index a17694d8a2e..f6992418e20 100644 --- a/shared-bindings/zlib/__init__.c +++ b/shared-bindings/zlib/__init__.c @@ -53,7 +53,7 @@ static mp_obj_t zlib_decompress(size_t n_args, const mp_obj_t *args) { mp_int_t wbits = 0; if (n_args > 1) { - wbits = MP_OBJ_SMALL_INT_VALUE(args[1]); + wbits = mp_obj_get_int_truncated(args[1]); } return common_hal_zlib_decompress(args[0], wbits); diff --git a/shared-module/_bleio/Attribute.c b/shared-module/_bleio/Attribute.c index b12f2b121de..cfc5999df1e 100644 --- a/shared-module/_bleio/Attribute.c +++ b/shared-module/_bleio/Attribute.c @@ -24,3 +24,14 @@ void common_hal_bleio_attribute_security_mode_check_valid(bleio_attribute_securi break; } } + +bool bleio_attribute_security_mode_requires_mitm(bleio_attribute_security_mode_t security_mode) { + switch (security_mode) { + case SECURITY_MODE_ENC_WITH_MITM: + case SECURITY_MODE_LESC_ENC_WITH_MITM: + case SECURITY_MODE_SIGNED_WITH_MITM: + return true; + default: + return false; + } +} diff --git a/shared-module/adafruit_bus_device/i2c_device/I2CDevice.c b/shared-module/adafruit_bus_device/i2c_device/I2CDevice.c index e01875452b1..0ed2d35be53 100644 --- a/shared-module/adafruit_bus_device/i2c_device/I2CDevice.c +++ b/shared-module/adafruit_bus_device/i2c_device/I2CDevice.c @@ -24,9 +24,7 @@ void common_hal_adafruit_bus_device_i2cdevice_lock(adafruit_bus_device_i2cdevice while (!mp_obj_is_true(success)) { RUN_BACKGROUND_TASKS; - if (mp_hal_is_interrupted()) { - break; - } + mp_handle_pending(true); success = mp_call_method_n_kw(0, 0, dest); } diff --git a/shared-module/audiocore/WaveFile.c b/shared-module/audiocore/WaveFile.c index 16bbf965e8b..6d648eeaf8c 100644 --- a/shared-module/audiocore/WaveFile.c +++ b/shared-module/audiocore/WaveFile.c @@ -11,6 +11,7 @@ #include "py/mperrno.h" #include "py/runtime.h" +#include "py/stream.h" #include "shared-module/audiocore/WaveFile.h" #include "shared-bindings/audiocore/__init__.h" @@ -29,37 +30,51 @@ struct wave_format_chunk { uint8_t extended_guid[14]; }; +// Read exactly size bytes from the stream or raise OSError on error or EOF. +static void wavefile_read_exactly(mp_obj_t stream, void *buf, size_t size) { + int errcode = 0; + mp_uint_t bytes_read = mp_stream_rw(stream, buf, size, &errcode, MP_STREAM_RW_READ); + if (errcode != 0) { + mp_raise_OSError(errcode); + } + if (bytes_read != size) { + mp_raise_OSError(MP_EIO); + } +} + +// Seek the stream and raise OSError on failure. Returns the new position. +static mp_off_t wavefile_seek(mp_obj_t stream, mp_off_t offset, int whence) { + int errcode; + mp_off_t position = mp_stream_seek(stream, offset, whence, &errcode); + if (position == (mp_off_t)-1) { + mp_raise_OSError(errcode); + } + return position; +} + void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t *self, - pyb_file_obj_t *file, + mp_obj_t file, uint8_t *buffer, size_t buffer_size) { + // Make sure the file supports reading and seeking. + mp_get_stream_raise(file, MP_STREAM_OP_READ | MP_STREAM_OP_IOCTL); + // Load the wave self->file = file; uint8_t chunk_header[16]; - f_rewind(&self->file->fp); - UINT bytes_read; - if (f_read(&self->file->fp, chunk_header, 16, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); - } - if (bytes_read != 16 || - memcmp(chunk_header, "RIFF", 4) != 0 || + wavefile_seek(file, 0, MP_SEEK_SET); + wavefile_read_exactly(file, chunk_header, 16); + if (memcmp(chunk_header, "RIFF", 4) != 0 || memcmp(chunk_header + 8, "WAVEfmt ", 8) != 0) { mp_arg_error_invalid(MP_QSTR_file); } uint32_t format_size; - if (f_read(&self->file->fp, &format_size, 4, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); - } - if (bytes_read != 4 || - format_size > sizeof(struct wave_format_chunk)) { + wavefile_read_exactly(file, &format_size, 4); + if (format_size > sizeof(struct wave_format_chunk)) { mp_raise_ValueError(MP_ERROR_TEXT("Invalid format chunk size")); } struct wave_format_chunk format; - if (f_read(&self->file->fp, &format, format_size, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); - } - if (bytes_read != format_size) { - } + wavefile_read_exactly(file, &format, format_size); if ((format_size != 40 && format.audio_format != 1) || format.num_channels > 2 || @@ -84,32 +99,21 @@ void common_hal_audioio_wavefile_construct(audioio_wavefile_obj_t *self, bool found_data_chunk = false; while (!found_data_chunk) { - if (f_read(&self->file->fp, &chunk_tag, 4, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); - } - if (bytes_read != 4) { - mp_raise_OSError(MP_EIO); - } + wavefile_read_exactly(file, chunk_tag, 4); if (memcmp((uint8_t *)chunk_tag, "data", 4) == 0) { found_data_chunk = true; } - if (f_read(&self->file->fp, &chunk_length, 4, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); - } - if (bytes_read != 4) { - mp_raise_OSError(MP_EIO); - } + wavefile_read_exactly(file, &chunk_length, 4); if (!found_data_chunk) { - if (f_lseek(&self->file->fp, f_tell(&self->file->fp) + chunk_length) != FR_OK) { - mp_raise_OSError(MP_EIO); - } + mp_off_t current_position = wavefile_seek(file, 0, MP_SEEK_CUR); + wavefile_seek(file, current_position + chunk_length, MP_SEEK_SET); } } self->file_length = chunk_length; - self->data_start = self->file->fp.fptr; + self->data_start = wavefile_seek(file, 0, MP_SEEK_CUR); // Try to allocate two buffers, one will be loaded from file and the other // DMAed to DAC. @@ -149,7 +153,7 @@ void audioio_wavefile_reset_buffer(audioio_wavefile_obj_t *self, // We don't reset the buffer index in case we're looping and we have an odd number of buffer // loads self->bytes_remaining = self->file_length; - f_lseek(&self->file->fp, self->data_start); + wavefile_seek(self->file, self->data_start, MP_SEEK_SET); self->read_count = 0; self->left_read_count = 0; self->right_read_count = 0; @@ -182,13 +186,15 @@ audioio_get_buffer_result_t audioio_wavefile_get_buffer(audioio_wavefile_obj_t * if (num_bytes_to_load > self->bytes_remaining) { num_bytes_to_load = self->bytes_remaining; } - UINT length_read; + mp_uint_t length_read; if (self->buffer_index % 2 == 1) { *buffer = self->second_buffer; } else { *buffer = self->buffer; } - if (f_read(&self->file->fp, *buffer, num_bytes_to_load, &length_read) != FR_OK || length_read != num_bytes_to_load) { + int errcode = 0; + length_read = mp_stream_rw(self->file, *buffer, num_bytes_to_load, &errcode, MP_STREAM_RW_ONCE); + if (errcode != 0 || length_read != num_bytes_to_load) { return GET_BUFFER_ERROR; } self->bytes_remaining -= length_read; diff --git a/shared-module/audiocore/WaveFile.h b/shared-module/audiocore/WaveFile.h index 74e25ffebfe..49250ca16f4 100644 --- a/shared-module/audiocore/WaveFile.h +++ b/shared-module/audiocore/WaveFile.h @@ -6,7 +6,6 @@ #pragma once -#include "extmod/vfs_fat.h" #include "py/obj.h" #include "shared-module/audiocore/__init__.h" @@ -18,12 +17,12 @@ typedef struct { uint8_t *second_buffer; uint32_t second_buffer_length; uint32_t file_length; // In bytes - uint16_t data_start; // Where the data values start + uint32_t data_start; // Where the data values start uint16_t buffer_index; uint32_t bytes_remaining; uint32_t len; - pyb_file_obj_t *file; + mp_obj_t file; uint32_t read_count; uint32_t left_read_count; diff --git a/shared-module/audiodelays/Chorus.c b/shared-module/audiodelays/Chorus.c index f9dd627c375..7033df0e272 100644 --- a/shared-module/audiodelays/Chorus.c +++ b/shared-module/audiodelays/Chorus.c @@ -130,6 +130,10 @@ void chorus_recalculate_delay(audiodelays_chorus_obj_t *self, mp_float_t f_delay // Calculate the current chorus buffer length in bytes uint32_t new_chorus_buffer_len = (uint32_t)(self->base.sample_rate / MICROPY_FLOAT_CONST(1000.0) * f_delay_ms) * (self->base.channel_count * sizeof(uint16_t)); + if (new_chorus_buffer_len > self->max_chorus_buffer_len) { + new_chorus_buffer_len = self->max_chorus_buffer_len; + } + self->chorus_buffer_len = new_chorus_buffer_len; self->current_delay_ms = f_delay_ms; diff --git a/shared-module/audiodelays/Echo.c b/shared-module/audiodelays/Echo.c index a57ed237c48..675c7cb5ebc 100644 --- a/shared-module/audiodelays/Echo.c +++ b/shared-module/audiodelays/Echo.c @@ -143,12 +143,13 @@ void recalculate_delay(audiodelays_echo_obj_t *self, mp_float_t f_delay_ms) { uint32_t new_echo_buffer_len = (uint32_t)(self->base.sample_rate / MICROPY_FLOAT_CONST(1000.0) * f_delay_ms) * sizeof(uint16_t); // Limit to valid range - if (new_echo_buffer_len > max_echo_buffer_len) { - new_echo_buffer_len = max_echo_buffer_len; - } else if (new_echo_buffer_len < self->buffer_len) { + if (new_echo_buffer_len < self->buffer_len) { // If the echo buffer is smaller than our audio buffer, weird things happen new_echo_buffer_len = self->buffer_len; } + if (new_echo_buffer_len > max_echo_buffer_len) { + new_echo_buffer_len = max_echo_buffer_len; + } self->echo_buffer_len = new_echo_buffer_len; diff --git a/shared-module/audiodelays/GranularPitchShift.c b/shared-module/audiodelays/GranularPitchShift.c index 0408dd45b27..a737307d553 100644 --- a/shared-module/audiodelays/GranularPitchShift.c +++ b/shared-module/audiodelays/GranularPitchShift.c @@ -326,7 +326,9 @@ audioio_get_buffer_result_t audiodelays_granular_pitch_shift_get_buffer(audiodel } else { // For unsigned samples set to the middle which is "quiet" if (MP_LIKELY(self->base.bits_per_sample == 16)) { - memset(word_buffer, 32768, length * (self->base.bits_per_sample / 8)); + for (uint32_t i = 0; i < length; i++) { + word_buffer[i] = (int16_t)0x8000; + } } else { memset(hword_buffer, 128, length * (self->base.bits_per_sample / 8)); } diff --git a/shared-module/audiodelays/MultiTapDelay.c b/shared-module/audiodelays/MultiTapDelay.c index 39887832764..d4cda65b0ed 100644 --- a/shared-module/audiodelays/MultiTapDelay.c +++ b/shared-module/audiodelays/MultiTapDelay.c @@ -118,12 +118,13 @@ void common_hal_audiodelays_multi_tap_delay_set_delay_ms(audiodelays_multi_tap_d self->delay_buffer_len = (uint32_t)(self->base.sample_rate / MICROPY_FLOAT_CONST(1000.0) * self->delay_ms) * (self->base.channel_count * sizeof(uint16_t)); // Limit to valid range - if (self->delay_buffer_len > self->max_delay_buffer_len) { - self->delay_buffer_len = self->max_delay_buffer_len; - } else if (self->delay_buffer_len < self->buffer_len) { + if (self->delay_buffer_len < self->buffer_len) { // If the delay buffer is smaller than our audio buffer, weird things happen self->delay_buffer_len = self->buffer_len; } + if (self->delay_buffer_len > self->max_delay_buffer_len) { + self->delay_buffer_len = self->max_delay_buffer_len; + } // Clear the now unused part of the buffer or some weird artifacts appear memset(self->delay_buffer + self->delay_buffer_len, 0, self->max_delay_buffer_len - self->delay_buffer_len); diff --git a/shared-module/audiodelays/PitchShift.c b/shared-module/audiodelays/PitchShift.c index 42925fbf6ae..809efaf77b1 100644 --- a/shared-module/audiodelays/PitchShift.c +++ b/shared-module/audiodelays/PitchShift.c @@ -61,6 +61,7 @@ void common_hal_audiodelays_pitch_shift_construct(audiodelays_pitch_shift_obj_t self->freeze = false; // Allocate the window buffer + mp_arg_validate_int_min(window, sizeof(uint16_t) * channel_count, MP_QSTR_window); self->window_len = window; // bytes self->window_buffer = m_malloc_without_collect(self->window_len); if (self->window_buffer == NULL) { @@ -233,7 +234,9 @@ audioio_get_buffer_result_t audiodelays_pitch_shift_get_buffer(audiodelays_pitch } else { // For unsigned samples set to the middle which is "quiet" if (MP_LIKELY(self->base.bits_per_sample == 16)) { - memset(word_buffer, 32768, length * (self->base.bits_per_sample / 8)); + for (uint32_t si = 0; si < length; si++) { + word_buffer[si] = (int16_t)0x8000; + } } else { memset(hword_buffer, 128, length * (self->base.bits_per_sample / 8)); } diff --git a/shared-module/audiofilters/Distortion.c b/shared-module/audiofilters/Distortion.c index cf7125c5cee..1498c402ea0 100644 --- a/shared-module/audiofilters/Distortion.c +++ b/shared-module/audiofilters/Distortion.c @@ -210,7 +210,9 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist } else { // For unsigned samples set to the middle which is "quiet" if (MP_LIKELY(self->base.bits_per_sample == 16)) { - memset(word_buffer, 32768, length * (self->base.bits_per_sample / 8)); + for (uint32_t si = 0; si < length; si++) { + word_buffer[si] = (int16_t)0x8000; + } } else { memset(hword_buffer, 128, length * (self->base.bits_per_sample / 8)); } @@ -325,7 +327,7 @@ audioio_get_buffer_result_t audiofilters_distortion_get_buffer(audiofilters_dist // Hard clip if (!self->soft_clip) { - word = MIN(MAX(word, -32767), 32768); + word = MIN(MAX(word, -32767), 32767); } if (MP_LIKELY(self->base.bits_per_sample == 16)) { diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index a5351edecc4..37deaa06b58 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -225,7 +225,7 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o } // Process biquad filters - for (uint8_t j = 0; j < self->filter.objs_len; j++) { + for (size_t j = 0; j < self->filter.objs_len; j++) { mp_obj_t filter_obj = self->filter.objs[j]; common_hal_synthio_biquad_tick(filter_obj); for (uint8_t k = 0; k < self->base.channel_count; k++) { diff --git a/shared-module/audiofilters/__init__.c b/shared-module/audiofilters/__init__.c index 4b2ec0ed063..7f23afcc59c 100644 --- a/shared-module/audiofilters/__init__.c +++ b/shared-module/audiofilters/__init__.c @@ -48,21 +48,21 @@ void audiofilters_assign_filter_chain(audiofilters_filter_chain_t *self, mp_obj_ void audiofilters_reset_filter_chain(audiofilters_filter_chain_t *self, uint8_t channel_count) { if (self->states) { - for (uint8_t i = 0; i < self->objs_len * channel_count; i++) { + for (size_t i = 0; i < self->objs_len * channel_count; i++) { synthio_biquad_filter_reset(&self->states[i]); } } } void audiofilters_tick_filter_chain(audiofilters_filter_chain_t *self) { - for (uint8_t j = 0; j < self->objs_len; j++) { + for (size_t j = 0; j < self->objs_len; j++) { common_hal_synthio_biquad_tick(self->objs[j]); } } int32_t audiofilters_process_filter_chain(audiofilters_filter_chain_t *self, uint8_t channel_count, uint8_t channel, int32_t word) { // Process biquad filters - for (uint8_t j = 0; j < self->objs_len; j++) { + for (size_t j = 0; j < self->objs_len; j++) { word = synthio_biquad_filter_sample(self->objs[j], &self->states[j * channel_count + channel], word); } return word; diff --git a/shared-module/audiofreeverb/Freeverb.c b/shared-module/audiofreeverb/Freeverb.c index 85aefb6fc61..08498a82c86 100644 --- a/shared-module/audiofreeverb/Freeverb.c +++ b/shared-module/audiofreeverb/Freeverb.c @@ -96,7 +96,7 @@ void common_hal_audiofreeverb_freeverb_construct(audiofreeverb_freeverb_obj_t *s common_hal_audiofreeverb_freeverb_deinit(self); m_malloc_fail(self->combbuffersizes[i]); } - memset(self->combbuffers[i], 0, self->combbuffersizes[i]); + memset(self->combbuffers[i], 0, self->combbuffersizes[i] * sizeof(uint16_t)); self->combbufferindex[i] = 0; self->combfitlers[i] = 0; @@ -114,7 +114,7 @@ void common_hal_audiofreeverb_freeverb_construct(audiofreeverb_freeverb_obj_t *s common_hal_audiofreeverb_freeverb_deinit(self); m_malloc_fail(self->allpassbuffersizes[i]); } - memset(self->allpassbuffers[i], 0, self->allpassbuffersizes[i]); + memset(self->allpassbuffers[i], 0, self->allpassbuffersizes[i] * sizeof(uint16_t)); self->allpassbufferindex[i] = 0; } @@ -304,6 +304,8 @@ audioio_get_buffer_result_t audiofreeverb_freeverb_get_buffer(audiofreeverb_free int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; + uint32_t channel_comb_offset = 0, channel_allpass_offset = 0; + for (uint32_t i = 0; i < n; i++) { int32_t sample_word = 0; if (self->sample != NULL) { @@ -312,7 +314,6 @@ audioio_get_buffer_result_t audiofreeverb_freeverb_get_buffer(audiofreeverb_free int32_t word, sum; int16_t input, bufout, output; - uint32_t channel_comb_offset = 0, channel_allpass_offset = 0; // Apply filters as Pre-EQ input = (int16_t)audiofilters_process_filter_chain(&self->pre_filter, self->base.channel_count, n % self->base.channel_count, sample_word); diff --git a/shared-module/audiomixer/Mixer.c b/shared-module/audiomixer/Mixer.c index a6c4397e8bf..464117fae8d 100644 --- a/shared-module/audiomixer/Mixer.c +++ b/shared-module/audiomixer/Mixer.c @@ -287,7 +287,7 @@ static void mix_down_one_voice(audiomixer_mixer_obj_t *self, word_buffer[i] = mult16signed(word, active_lo_level, active_hi_level); } } else { - for (uint32_t i = 0; i < n; i += 2) { + for (uint32_t i = 0; i + 1 < n; i += 2) { uint32_t word = src[i >> 1]; uint32_t word_lsb = copy16lsb(word); assignmul(word_lsb, &last_word, &active_lo_level, &active_hi_level, pending_lo_level, pending_hi_level); diff --git a/shared-module/audiomp3/MP3Decoder.c b/shared-module/audiomp3/MP3Decoder.c index 463fccf9708..04dd8d2d75c 100644 --- a/shared-module/audiomp3/MP3Decoder.c +++ b/shared-module/audiomp3/MP3Decoder.c @@ -244,7 +244,8 @@ static void mp3file_skip_id3v2(audiomp3_mp3file_obj_t *self, bool block_ok) { size -= to_consume; // Next, seek in the file after the header - if (stream_lseek(self->stream, SEEK_CUR, size) == 0) { + off_t before = stream_lseek(self->stream, 0, SEEK_CUR); + if (before >= 0 && stream_lseek(self->stream, size, SEEK_CUR) == before + size) { return; } @@ -424,7 +425,7 @@ void audiomp3_mp3file_reset_buffer(audiomp3_mp3file_obj_t *self, // We don't reset the buffer index in case we're looping and we have an odd number of buffer // loads background_callback_prevent(); - if (self->eof && stream_lseek(self->stream, SEEK_SET, 0) == 0) { + if (self->eof && stream_lseek(self->stream, 0, SEEK_SET) == 0) { INPUT_BUFFER_CLEAR(self->inbuf); self->eof = 0; self->samples_decoded = 0; @@ -491,8 +492,9 @@ audioio_get_buffer_result_t audiomp3_mp3file_get_buffer(audiomp3_mp3file_obj_t * if (self->eof || (err != ERR_MP3_INDATA_UNDERFLOW && err != ERR_MP3_MAINDATA_UNDERFLOW)) { memset(buffer, 0, self->base.max_buffer_length); *buffer_length = 0; + bool underflow = (err == ERR_MP3_INDATA_UNDERFLOW || err == ERR_MP3_MAINDATA_UNDERFLOW); self->eof = true; - return GET_BUFFER_ERROR; + return underflow ? GET_BUFFER_DONE : GET_BUFFER_ERROR; } } diff --git a/shared-module/aurora_epaper/aurora_framebuffer.c b/shared-module/aurora_epaper/aurora_framebuffer.c index 65b08e15b1c..6ca1c7b732e 100644 --- a/shared-module/aurora_epaper/aurora_framebuffer.c +++ b/shared-module/aurora_epaper/aurora_framebuffer.c @@ -69,34 +69,46 @@ void common_hal_aurora_epaper_framebuffer_construct( // CS common_hal_digitalio_digitalinout_construct(&self->chip_select, chip_select); common_hal_digitalio_digitalinout_switch_to_output(&self->chip_select, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(chip_select); + #endif // RST common_hal_digitalio_digitalinout_construct(&self->reset, reset); common_hal_digitalio_digitalinout_switch_to_output(&self->reset, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(reset); + #endif // BUSY common_hal_digitalio_digitalinout_construct(&self->busy, busy); common_hal_digitalio_digitalinout_switch_to_input(&self->busy, PULL_NONE); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(busy); + #endif // DC common_hal_digitalio_digitalinout_construct(&self->discharge, discharge); common_hal_digitalio_digitalinout_switch_to_output(&self->discharge, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(discharge); + #endif // Power pin (if set) if (power != NULL) { common_hal_digitalio_digitalinout_construct(&self->power, power); common_hal_digitalio_digitalinout_switch_to_output(&self->power, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(power); + #endif } else { self->power.pin = NULL; } self->bus = spi; + #if CIRCUITPY_BULK_RESET common_hal_busio_spi_never_reset(self->bus); + #endif self->width = width; self->height = height; @@ -535,13 +547,11 @@ void common_hal_aurora_epaper_framebuffer_deinit(aurora_epaper_framebuffer_obj_t common_hal_busio_spi_deinit(self->bus); } - common_hal_reset_pin(self->chip_select.pin); - common_hal_reset_pin(self->reset.pin); - common_hal_reset_pin(self->busy.pin); - common_hal_reset_pin(self->discharge.pin); - if (self->power.pin != NULL) { - common_hal_reset_pin(self->power.pin); - } + common_hal_digitalio_digitalinout_deinit(&self->chip_select); + common_hal_digitalio_digitalinout_deinit(&self->reset); + common_hal_digitalio_digitalinout_deinit(&self->busy); + common_hal_digitalio_digitalinout_deinit(&self->discharge); + common_hal_digitalio_digitalinout_deinit(&self->power); port_free(self->bufinfo.buf); port_free(self->pframe.buf); diff --git a/shared-module/bitbangio/I2C.c b/shared-module/bitbangio/I2C.c index d88455729e2..84a33f16673 100644 --- a/shared-module/bitbangio/I2C.c +++ b/shared-module/bitbangio/I2C.c @@ -251,7 +251,7 @@ uint8_t shared_module_bitbangio_i2c_write(bitbangio_i2c_obj_t *self, uint16_t ad if (status == 0) { for (uint32_t i = 0; i < len; i++) { - if (!write_byte(self, data[i])) { + if (write_byte(self, data[i]) <= 0) { status = MP_EIO; break; } @@ -271,7 +271,10 @@ uint8_t shared_module_bitbangio_i2c_read(bitbangio_i2c_obj_t *self, uint16_t add return MP_EIO; } uint8_t status = 0; - if (!write_byte(self, (addr << 1) | 1)) { + int addr_result = write_byte(self, (addr << 1) | 1); + if (addr_result < 0) { + status = MP_EIO; + } else if (!addr_result) { status = MP_ENODEV; } diff --git a/shared-module/bitbangio/SPI.c b/shared-module/bitbangio/SPI.c index 9b89d1d4a1b..854a1cb5118 100644 --- a/shared-module/bitbangio/SPI.c +++ b/shared-module/bitbangio/SPI.c @@ -145,7 +145,7 @@ bool shared_module_bitbangio_spi_write(bitbangio_spi_obj_t *self, const uint8_t uint8_t data_out = data[i]; for (int j = 0; j < 8; ++j, data_out <<= 1) { if (i == 0 && j == 0) { - if (!digitalinout_protocol_set_value(self->mosi, (data_out >> 7) & 1)) { + if (digitalinout_protocol_set_value(self->mosi, (data_out >> 7) & 1) != 0) { return false; } } else { @@ -209,7 +209,7 @@ bool shared_module_bitbangio_spi_read(bitbangio_spi_obj_t *self, uint8_t *data, } #endif if (self->has_mosi) { - if (!digitalinout_protocol_set_value(self->mosi, false)) { + if (digitalinout_protocol_set_value(self->mosi, false) != 0) { return false; } } @@ -298,7 +298,7 @@ bool shared_module_bitbangio_spi_transfer(bitbangio_spi_obj_t *self, const uint8 uint8_t data_in = 0; for (int j = 0; j < 8; ++j, data_out <<= 1) { if (i == 0 && j == 0) { - if (!digitalinout_protocol_set_value(self->mosi, (data_out >> 7) & 1)) { + if (digitalinout_protocol_set_value(self->mosi, (data_out >> 7) & 1) != 0) { return false; } } else { diff --git a/shared-module/bitmaptools/__init__.c b/shared-module/bitmaptools/__init__.c index e51881aa5bb..bcfbac3cf71 100644 --- a/shared-module/bitmaptools/__init__.c +++ b/shared-module/bitmaptools/__init__.c @@ -747,10 +747,10 @@ static void fill_row(displayio_bitmap_t *bitmap, int swap, int16_t *luminance_da static void write_pixels(displayio_bitmap_t *bitmap, int y, bool *data) { if (bitmap->bits_per_value == 1) { - uint32_t *pixel_data = (uint32_t *)(bitmap->data + bitmap->stride * y); - for (int i = 0; i < bitmap->width; i++) { - uint32_t p = 0; - for (int j = 0; j < 32; j++) { + uint8_t *pixel_data = (uint8_t *)(bitmap->data + bitmap->stride * y); + for (int i = 0; i < (bitmap->width + 7) / 8; i++) { + uint8_t p = 0; + for (int j = 0; j < 8; j++) { p = (p << 1); if (*data++) { p |= 1; @@ -787,6 +787,7 @@ void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bi }; // out holds one output row of pixels, and is padded to be a multiple of 32 so that the 1bpp storage loop can be simplified bool out[(width + 31) / 32 * 32]; + memset(out + width, 0, sizeof(out) - width); fill_row(source_bitmap, swap, rows[0], 0, info->mx); fill_row(source_bitmap, swap, rows[1], 1, info->mx); @@ -1080,6 +1081,51 @@ void common_hal_bitmaptools_draw_circle(displayio_bitmap_t *destination, draw_circle(destination, x, y, radius, value); } +// Copy whole rows with memmove when no per-pixel decisions are needed. +static bool blit_rows_fast(displayio_bitmap_t *destination, displayio_bitmap_t *source, + int16_t x, int16_t y, int16_t x1, int16_t y1, int16_t x2, int16_t y2) { + uint8_t bits_per_value = source->bits_per_value; + if (bits_per_value != destination->bits_per_value || bits_per_value < 8) { + return false; + } + if (x1 < 0 || y1 < 0 || x2 > source->width || y2 > source->height) { + return false; + } + int sx = x1, sy = y1, dx = x, dy = y; + int w = x2 - x1, h = y2 - y1; + if (dx < 0) { + sx -= dx; + w += dx; + dx = 0; + } + if (dy < 0) { + sy -= dy; + h += dy; + dy = 0; + } + if (dx + w > destination->width) { + w = destination->width - dx; + } + if (dy + h > destination->height) { + h = destination->height - dy; + } + if (w <= 0 || h <= 0) { + return true; // nothing lands in the destination + } + size_t bytes_per_value = bits_per_value / 8; + size_t row_bytes = (size_t)w * bytes_per_value; + // When a bitmap is blitted onto itself and the destination rows sit below the + // source rows, copy bottom-up so no source row is overwritten before it is read. + bool bottom_up = destination == source && dy > sy; + for (int j = 0; j < h; j++) { + int row = bottom_up ? h - 1 - j : j; + const uint8_t *src_row = (const uint8_t *)(source->data + (size_t)(sy + row) * source->stride) + (size_t)sx * bytes_per_value; + uint8_t *dst_row = (uint8_t *)(destination->data + (size_t)(dy + row) * destination->stride) + (size_t)dx * bytes_per_value; + memmove(dst_row, src_row, row_bytes); + } + return true; +} + void common_hal_bitmaptools_blit(displayio_bitmap_t *destination, displayio_bitmap_t *source, int16_t x, int16_t y, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint32_t skip_source_index, bool skip_source_index_none, uint32_t skip_dest_index, bool skip_dest_index_none) { @@ -1116,6 +1162,11 @@ void common_hal_bitmaptools_blit(displayio_bitmap_t *destination, displayio_bitm y_reverse = true; } + if (skip_source_index_none && skip_dest_index_none && + blit_rows_fast(destination, source, x, y, x1, y1, x2, y2)) { + return; + } + // simplest version - use internal functions for get/set pixels for (int16_t i = 0; i < (x2 - x1); i++) { diff --git a/shared-module/busdisplay/BusDisplay.c b/shared-module/busdisplay/BusDisplay.c index 01e0f7a7896..b4eb4c61dad 100644 --- a/shared-module/busdisplay/BusDisplay.c +++ b/shared-module/busdisplay/BusDisplay.c @@ -110,17 +110,23 @@ void common_hal_busdisplay_busdisplay_construct(busdisplay_busdisplay_obj_t *sel if (result != PWMOUT_OK) { self->backlight_inout.base.type = &digitalio_digitalinout_type; common_hal_digitalio_digitalinout_construct(&self->backlight_inout, backlight_pin); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(backlight_pin); + #endif } else { self->backlight_pwm.base.type = &pwmio_pwmout_type; + #if CIRCUITPY_BULK_RESET common_hal_pwmio_pwmout_never_reset(&self->backlight_pwm); + #endif } #else // Otherwise default to digital self->backlight_inout.base.type = &digitalio_digitalinout_type; common_hal_digitalio_digitalinout_construct(&self->backlight_inout, backlight_pin); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(backlight_pin); #endif + #endif } common_hal_busdisplay_busdisplay_set_brightness(self, brightness); @@ -218,6 +224,30 @@ static void _send_pixels(busdisplay_busdisplay_obj_t *self, uint8_t *pixels, uin self->bus.send(self->bus.bus, DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, pixels, length); } +// Whether the bus sends in the background. Always false without async SPI, so that code is +// left out. Not a local variable: GCC would then compile the refresh loop twice. +#define SEND_ASYNC(self) (CIRCUITPY_BUSIO_SPI_ASYNC && (self)->bus.send_async != NULL) + +// The data half of _send_pixels, handed to the bus without waiting for it to be sent. +static void _send_pixels_async(busdisplay_busdisplay_obj_t *self, uint8_t *pixels, uint32_t length) { + if (!self->bus.data_as_commands) { + self->bus.send(self->bus.bus, DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, &self->write_ram_command, 1); + } + self->bus.send_async(self->bus.bus, DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, pixels, length); +} + +// Call only with the bus transaction closed: a background task may use the same bus. +static void _run_background_tasks(void) { + // Run background tasks so they can run during an explicit refresh. + // Auto-refresh won't run background tasks here because it is a background task itself. + RUN_BACKGROUND_TASKS; + + // Run USB background tasks so they can run during an implicit refresh. + #if CIRCUITPY_TINYUSB + usb_background(); + #endif +} + static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_area_t *area) { uint16_t buffer_size = CIRCUITPY_DISPLAY_AREA_BUFFER_SIZE / sizeof(uint32_t); // In uint32_ts @@ -227,6 +257,7 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are return true; } uint16_t rows_per_buffer = displayio_area_height(&clipped); + uint16_t cols_per_buffer = displayio_area_width(&clipped); uint8_t pixels_per_word = (sizeof(uint32_t) * 8) / self->core.colorspace.depth; uint16_t pixels_per_buffer = displayio_area_size(&clipped); @@ -237,47 +268,51 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are subrectangles = rows_per_buffer / 8; // page addressing mode writes 8 rows at a time rows_per_buffer = 8; } else if (displayio_area_size(&clipped) > buffer_size * pixels_per_word) { - rows_per_buffer = buffer_size * pixels_per_word / displayio_area_width(&clipped); - if (rows_per_buffer == 0) { - rows_per_buffer = 1; - } - // If pixels are packed by column then ensure rows_per_buffer is on a byte boundary. - if (self->core.colorspace.depth < 8 && !self->core.colorspace.pixels_in_byte_share_row) { - uint8_t pixels_per_byte = 8 / self->core.colorspace.depth; - if (rows_per_buffer % pixels_per_byte != 0) { - rows_per_buffer -= rows_per_buffer % pixels_per_byte; + uint16_t buffer_pixels = buffer_size * pixels_per_word; + if (self->core.transform.transpose_xy && self->core.colorspace.depth >= 8) { + // Rotated 90 or 270: a panel column is a source row. Send bands of columns so sources + // are read along their rows, which OnDiskBitmap needs to avoid a file seek per pixel. + rows_per_buffer = MIN(rows_per_buffer, buffer_pixels); + cols_per_buffer = buffer_pixels / rows_per_buffer; + } else { + rows_per_buffer = buffer_pixels / cols_per_buffer; + if (rows_per_buffer == 0) { + rows_per_buffer = 1; + } + // If pixels are packed by column then ensure rows_per_buffer is on a byte boundary. + if (self->core.colorspace.depth < 8 && !self->core.colorspace.pixels_in_byte_share_row) { + uint8_t pixels_per_byte = 8 / self->core.colorspace.depth; + if (rows_per_buffer % pixels_per_byte != 0) { + rows_per_buffer -= rows_per_buffer % pixels_per_byte; + } } } - subrectangles = displayio_area_height(&clipped) / rows_per_buffer; - if (displayio_area_height(&clipped) % rows_per_buffer != 0) { - subrectangles++; - } - pixels_per_buffer = rows_per_buffer * displayio_area_width(&clipped); - buffer_size = pixels_per_buffer / pixels_per_word; - if (pixels_per_buffer % pixels_per_word) { - buffer_size += 1; - } + subrectangles = ((displayio_area_height(&clipped) + rows_per_buffer - 1) / rows_per_buffer) * + ((displayio_area_width(&clipped) + cols_per_buffer - 1) / cols_per_buffer); + pixels_per_buffer = rows_per_buffer * cols_per_buffer; + buffer_size = (pixels_per_buffer + pixels_per_word - 1) / pixels_per_word; } // Allocated and shared as a uint32_t array so the compiler knows the // alignment everywhere. uint32_t mask_length = (pixels_per_buffer / 32) + 1; - uint32_t buffer[buffer_size]; + // With an asynchronous bus, one buffer is composited while the other is being sent. + uint32_t buffers[SEND_ASYNC(self) ? 2 : 1][buffer_size]; uint32_t mask[mask_length]; + uint8_t fill_index = 0; + bool sending = false; - uint16_t remaining_rows = displayio_area_height(&clipped); + displayio_area_t subrectangle = clipped; + subrectangle.next = NULL; for (uint16_t j = 0; j < subrectangles; j++) { - displayio_area_t subrectangle = { - .x1 = clipped.x1, - .y1 = clipped.y1 + rows_per_buffer * j, - .x2 = clipped.x2, - .y2 = clipped.y1 + rows_per_buffer * (j + 1) - }; - if (remaining_rows < rows_per_buffer) { - subrectangle.y2 = subrectangle.y1 + remaining_rows; + // Down the rows of a band first, then to the next band of columns. + if (subrectangle.y1 >= clipped.y2) { + subrectangle.y1 = clipped.y1; + subrectangle.x1 += cols_per_buffer; } - remaining_rows -= rows_per_buffer; + subrectangle.x2 = MIN(subrectangle.x1 + cols_per_buffer, clipped.x2); + subrectangle.y2 = MIN(subrectangle.y1 + rows_per_buffer, clipped.y2); uint16_t subrectangle_size_bytes; if (self->core.colorspace.depth >= 8) { @@ -286,32 +321,45 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are subrectangle_size_bytes = displayio_area_size(&subrectangle) / (8 / self->core.colorspace.depth); } + uint32_t *buffer = buffers[fill_index]; memset(mask, 0, mask_length * sizeof(mask[0])); memset(buffer, 0, buffer_size * sizeof(buffer[0])); displayio_display_core_fill_area(&self->core, &subrectangle, mask, buffer); + // The previous strip must be sent before the region commands for this one. + if (sending) { + displayio_display_bus_flush(&self->bus); + displayio_display_bus_end_transaction(&self->bus); + sending = false; + _run_background_tasks(); + } + displayio_display_bus_set_region_to_update(&self->bus, &self->core, &subrectangle); // Can't acquire display bus; skip the rest of the data. if (!displayio_display_bus_begin_transaction(&self->bus)) { return false; } - _send_pixels(self, (uint8_t *)buffer, subrectangle_size_bytes); - displayio_display_bus_end_transaction(&self->bus); - - // Run background tasks so they can run during an explicit refresh. - // Auto-refresh won't run background tasks here because it is a background task itself. - RUN_BACKGROUND_TASKS; - - // Run USB background tasks so they can run during an implicit refresh. - #if CIRCUITPY_TINYUSB - usb_background(); - #endif + if (SEND_ASYNC(self)) { + _send_pixels_async(self, (uint8_t *)buffer, subrectangle_size_bytes); + sending = true; + fill_index ^= 1; + } else { + _send_pixels(self, (uint8_t *)buffer, subrectangle_size_bytes); + displayio_display_bus_end_transaction(&self->bus); + } + subrectangle.y1 = subrectangle.y2; + if (!sending) { + _run_background_tasks(); + } } // Drain any remaining asynchronous transfers. displayio_display_bus_flush(&self->bus); + if (sending) { + displayio_display_bus_end_transaction(&self->bus); + } return true; } diff --git a/shared-module/displayio/Bitmap.c b/shared-module/displayio/Bitmap.c index acc41b9eef6..29f32fd40ce 100644 --- a/shared-module/displayio/Bitmap.c +++ b/shared-module/displayio/Bitmap.c @@ -63,7 +63,7 @@ void common_hal_displayio_bitmap_construct_from_buffer(displayio_bitmap_t *self, } void common_hal_displayio_bitmap_deinit(displayio_bitmap_t *self) { - if (self->data_alloc) { + if (self->data_alloc && self->data != NULL) { gc_free(self->data); } self->data = NULL; @@ -92,8 +92,8 @@ uint32_t common_hal_displayio_bitmap_get_pixel(displayio_bitmap_t *self, int16_t int32_t row_start = y * self->stride; uint32_t *row = self->data + row_start; uint8_t bytes_per_value = self->bits_per_value / 8; - uint8_t values_per_byte = 8 / self->bits_per_value; if (bytes_per_value < 1) { + uint8_t values_per_byte = 8 / self->bits_per_value; uint8_t bits = ((uint8_t *)row)[x >> self->x_shift]; uint8_t bit_position = (values_per_byte - (x & self->x_mask) - 1) * self->bits_per_value; return (bits >> bit_position) & self->bitmask; diff --git a/shared-module/displayio/OnDiskBitmap.c b/shared-module/displayio/OnDiskBitmap.c index 9abe8cf4fe1..44993b2ffcf 100644 --- a/shared-module/displayio/OnDiskBitmap.c +++ b/shared-module/displayio/OnDiskBitmap.c @@ -15,6 +15,7 @@ #include "py/mperrno.h" #include "py/runtime.h" +#include "py/stream.h" #define DISPLAYIO_ODBMP_DEBUG(...) (void)0 @@ -25,19 +26,23 @@ static uint32_t read_word(uint16_t *bmp_header, uint16_t index) { return bmp_header[index] | bmp_header[index + 1] << 16; } -void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, pyb_file_obj_t *file) { - // Load the wave +void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, mp_obj_t file) { self->file = file; + self->cache_len = 0; uint16_t bmp_header[69]; - f_rewind(&self->file->fp); - UINT bytes_read; + int errcode = 0; + + mp_stream_seek(file, 0, MP_SEEK_SET, &errcode); + if (errcode != 0) { + mp_raise_OSError(errcode); + } // Read the minimum amount of bytes required to parse a BITMAPCOREHEADER. // If needed, we will read more bytes down below. - if (f_read(&self->file->fp, bmp_header, 26, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); + mp_uint_t bytes_read = mp_stream_rw(file, bmp_header, 26, &errcode, MP_STREAM_RW_ONCE); + if (errcode != 0) { + mp_raise_OSError(errcode); } - DISPLAYIO_ODBMP_DEBUG("bytes_read: %d\n", bytes_read); if (bytes_read != 26 || memcmp(bmp_header, "BM", 2) != 0) { mp_arg_error_invalid(MP_QSTR_file); } @@ -48,8 +53,9 @@ void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, if (header_size == 40 || header_size == 108 || header_size == 124) { // Read the remaining header bytes - if (f_read(&self->file->fp, bmp_header + 13, header_size - 12, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); + bytes_read = mp_stream_rw(file, bmp_header + 13, header_size - 12, &errcode, MP_STREAM_RW_ONCE); + if (errcode != 0) { + mp_raise_OSError(errcode); } DISPLAYIO_ODBMP_DEBUG("bytes_read: %d\n", bytes_read); if (bytes_read != (header_size - 12)) { @@ -116,12 +122,14 @@ void common_hal_displayio_ondiskbitmap_construct(displayio_ondiskbitmap_t *self, uint32_t *palette_data = m_malloc_without_collect(palette_size); - f_rewind(&self->file->fp); - f_lseek(&self->file->fp, palette_offset); + mp_stream_seek(file, palette_offset, MP_SEEK_SET, &errcode); + if (errcode != 0) { + mp_raise_OSError(errcode); + } - UINT palette_bytes_read; - if (f_read(&self->file->fp, palette_data, palette_size, &palette_bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); + mp_uint_t palette_bytes_read = mp_stream_rw(file, palette_data, palette_size, &errcode, MP_STREAM_RW_ONCE); + if (errcode != 0) { + mp_raise_OSError(errcode); } if (palette_bytes_read != palette_size) { mp_raise_ValueError(MP_ERROR_TEXT("Unable to read color palette data")); @@ -175,40 +183,47 @@ uint32_t common_hal_displayio_ondiskbitmap_get_pixel(displayio_ondiskbitmap_t *s } else { location = self->data_offset + (self->height - y - 1) * self->stride + x / pixels_per_byte; } - // We don't cache here because the underlying FS caches sectors. - f_lseek(&self->file->fp, location); - UINT bytes_read; + int errcode = 0; + if (location < self->cache_start || location + bytes_per_pixel > self->cache_start + self->cache_len) { + mp_stream_seek(self->file, location, MP_SEEK_SET, &errcode); + if (errcode != 0) { + return 0; + } + self->cache_start = location; + self->cache_len = mp_stream_rw(self->file, self->cache, sizeof(self->cache), &errcode, MP_STREAM_RW_ONCE); + if (errcode != 0 || self->cache_len < bytes_per_pixel) { + self->cache_len = 0; + return 0; + } + } uint32_t pixel_data = 0; - uint32_t result = f_read(&self->file->fp, &pixel_data, bytes_per_pixel, &bytes_read); - if (result == FR_OK) { - uint32_t tmp = 0; - uint8_t red; - uint8_t green; - uint8_t blue; - if (bytes_per_pixel == 1) { - uint8_t offset = (x % pixels_per_byte) * self->bits_per_pixel; - uint8_t mask = (1 << self->bits_per_pixel) - 1; - - return (pixel_data >> ((8 - self->bits_per_pixel) - offset)) & mask; - } else if (bytes_per_pixel == 2) { - if (self->g_bitmask == 0x07e0) { // 565 - red = ((pixel_data & self->r_bitmask) >> 11); - green = ((pixel_data & self->g_bitmask) >> 5); - blue = ((pixel_data & self->b_bitmask) >> 0); - } else { // 555 - red = ((pixel_data & self->r_bitmask) >> 10); - green = ((pixel_data & self->g_bitmask) >> 4); - blue = ((pixel_data & self->b_bitmask) >> 0); - } - tmp = (red << 19 | green << 10 | blue << 3); - return tmp; - } else if ((bytes_per_pixel == 4) && (self->bitfield_compressed)) { - return pixel_data & 0x00FFFFFF; - } else { - return pixel_data; + memcpy(&pixel_data, self->cache + (location - self->cache_start), bytes_per_pixel); + uint32_t tmp = 0; + uint8_t red; + uint8_t green; + uint8_t blue; + if (bytes_per_pixel == 1) { + uint8_t offset = (x % pixels_per_byte) * self->bits_per_pixel; + uint8_t mask = (1 << self->bits_per_pixel) - 1; + + return (pixel_data >> ((8 - self->bits_per_pixel) - offset)) & mask; + } else if (bytes_per_pixel == 2) { + if (self->g_bitmask == 0x07e0) { // 565 + red = ((pixel_data & self->r_bitmask) >> 11); + green = ((pixel_data & self->g_bitmask) >> 5); + blue = ((pixel_data & self->b_bitmask) >> 0); + } else { // 555 + red = ((pixel_data & self->r_bitmask) >> 10); + green = ((pixel_data & self->g_bitmask) >> 4); + blue = ((pixel_data & self->b_bitmask) >> 0); } + tmp = (red << 19 | green << 10 | blue << 3); + return tmp; + } else if ((bytes_per_pixel == 4) && (self->bitfield_compressed)) { + return pixel_data & 0x00FFFFFF; + } else { + return pixel_data; } - return 0; } uint16_t common_hal_displayio_ondiskbitmap_get_height(displayio_ondiskbitmap_t *self) { diff --git a/shared-module/displayio/OnDiskBitmap.h b/shared-module/displayio/OnDiskBitmap.h index 6fe5b880cd6..220ace652f3 100644 --- a/shared-module/displayio/OnDiskBitmap.h +++ b/shared-module/displayio/OnDiskBitmap.h @@ -11,8 +11,6 @@ #include "py/obj.h" -#include "extmod/vfs_fat.h" - typedef struct { mp_obj_base_t base; uint16_t width; @@ -22,7 +20,7 @@ typedef struct { uint32_t r_bitmask; uint32_t g_bitmask; uint32_t b_bitmask; - pyb_file_obj_t *file; + mp_obj_t file; union { mp_obj_base_t *pixel_shader_base; struct displayio_palette *palette; @@ -30,4 +28,8 @@ typedef struct { }; bool bitfield_compressed; uint8_t bits_per_pixel; + // The last bytes read from the file. Pixels are read along rows, so most reads hit it. + uint8_t cache_len; + uint32_t cache_start; + uint8_t cache[64]; } displayio_ondiskbitmap_t; diff --git a/shared-module/displayio/TileGrid.c b/shared-module/displayio/TileGrid.c index b5c691ccbd6..beb524de678 100644 --- a/shared-module/displayio/TileGrid.c +++ b/shared-module/displayio/TileGrid.c @@ -425,6 +425,70 @@ void common_hal_displayio_tilegrid_set_top_left(displayio_tilegrid_t *self, uint self->full_change = true; } +// One row of the common case: an 8 or 16 bpp Bitmap, a Palette without dithering, a 16 bpp +// display, no scaling, no x flip and no transpose. Same result as the per-pixel loop in +// displayio_tilegrid_fill_area, without the per-pixel calls. Returns false if a pixel was +// transparent. +static bool _fill_row_fast(displayio_tilegrid_t *self, const void *tiles, + displayio_bitmap_t *bitmap, displayio_palette_t *palette, + const _displayio_colorspace_t *colorspace, uint32_t *mask, uint16_t *buffer, + int32_t offset, uint16_t x, uint16_t y, int16_t count, int16_t x_in_tile, + uint16_t x_tile_index, uint16_t row_tile_location, int16_t y_in_tile) { + bool covered = true; + uint16_t tile_width = self->tile_width; + while (count > 0) { + uint16_t tile_location = row_tile_location + x_tile_index; + // uint8_t like displayio_input_pixel_t.tile, which the per-pixel loop goes through. + uint8_t tile = (self->tiles_in_bitmap > 255) ? + ((const uint16_t *)tiles)[tile_location] : ((const uint8_t *)tiles)[tile_location]; + int16_t bx = (tile % self->bitmap_width_in_tiles) * tile_width + x_in_tile; + int16_t by = (tile / self->bitmap_width_in_tiles) * self->tile_height + y_in_tile; + int16_t run = tile_width - x_in_tile; + if (run > count) { + run = count; + } + bool row_inside = by >= 0 && by < bitmap->height; + const uint32_t *row = bitmap->data + by * bitmap->stride; + for (int16_t i = 0; i < run; i++, offset++, bx++, x++) { + uint32_t bit = 1u << (offset & 31); + if (mask[offset >> 5] & bit) { + continue; + } + uint32_t index = 0; // get_pixel returns 0 outside the bitmap + if (row_inside && bx >= 0 && bx < bitmap->width) { + index = (bitmap->bits_per_value == 8) ? + ((const uint8_t *)row)[bx] : ((const uint16_t *)row)[bx]; + } + if (index > palette->color_count || palette->colors[index].transparent) { + covered = false; + continue; + } + const _displayio_color_t *color = &palette->colors[index]; + uint16_t pixel; + if (color->cached_colorspace == colorspace && + color->cached_colorspace_grayscale_bit == colorspace->grayscale_bit && + color->cached_colorspace_grayscale == colorspace->grayscale) { + pixel = color->cached_color; + } else { + displayio_input_pixel_t input_pixel = { + .pixel = index, .x = x, .y = y, .tile = tile, .tile_x = bx, .tile_y = by, + }; + displayio_output_pixel_t output_pixel = { .pixel = 0, .opaque = true }; + displayio_palette_get_color(palette, colorspace, &input_pixel, &output_pixel); + pixel = output_pixel.pixel; + } + mask[offset >> 5] |= bit; + buffer[offset] = pixel; + } + count -= run; + x_in_tile = 0; + if (++x_tile_index == self->width_in_tiles) { + x_tile_index = 0; + } + } + return covered; +} + bool displayio_tilegrid_fill_area(displayio_tilegrid_t *self, const _displayio_colorspace_t *colorspace, const displayio_area_t *area, uint32_t *mask, uint32_t *buffer) { @@ -515,10 +579,61 @@ bool displayio_tilegrid_fill_area(displayio_tilegrid_t *self, displayio_input_pixel_t input_pixel; displayio_output_pixel_t output_pixel; + uint16_t scale = self->absolute_transform->scale; + uint16_t tile_width = self->tile_width; + uint16_t width_in_tiles = self->width_in_tiles; + + displayio_bitmap_t *row_bitmap = NULL; + displayio_palette_t *row_palette = NULL; + if (scale == 1 && x_stride == 1 && colorspace->depth == 16 && + self->transpose_xy == self->absolute_transform->transpose_xy && + mp_obj_is_type(self->bitmap, &displayio_bitmap_type) && + mp_obj_is_type(self->pixel_shader, &displayio_palette_type)) { + displayio_bitmap_t *bitmap = self->bitmap; + displayio_palette_t *palette = self->pixel_shader; + if ((bitmap->bits_per_value == 8 || bitmap->bits_per_value == 16) && !palette->dither) { + row_bitmap = bitmap; + row_palette = palette; + } + } + for (input_pixel.y = start_y; input_pixel.y < end_y; ++input_pixel.y) { int16_t row_start = start + (input_pixel.y - start_y + y_shift) * y_stride; // in pixels - int16_t local_y = input_pixel.y / self->absolute_transform->scale; + int16_t local_y = input_pixel.y / scale; + uint16_t y_tile_index = (local_y / self->tile_height + self->top_left_y) % self->height_in_tiles; + uint16_t row_tile_location = y_tile_index * width_in_tiles; + int16_t y_in_tile = local_y % self->tile_height; + + // The x terms are stepped along the row instead of divided for every pixel. + int16_t local_x = start_x / scale; + uint16_t x_in_scale = start_x % scale; + int16_t x_in_tile = local_x % tile_width; + uint16_t x_tile_index = (local_x / tile_width + self->top_left_x) % width_in_tiles; + int32_t cached_tile = -1; + int16_t tile_base_x = 0; + int16_t tile_base_y = 0; + + if (row_bitmap != NULL) { + if (!_fill_row_fast(self, tiles, row_bitmap, row_palette, colorspace, mask, + (uint16_t *)buffer, row_start + x_shift, start_x, input_pixel.y, + end_x - start_x, x_in_tile, x_tile_index, row_tile_location, y_in_tile)) { + full_coverage = false; + } + continue; + } + for (input_pixel.x = start_x; input_pixel.x < end_x; ++input_pixel.x) { + if (input_pixel.x != start_x && ++x_in_scale == scale) { + x_in_scale = 0; + local_x++; + if (++x_in_tile == tile_width) { + x_in_tile = 0; + if (++x_tile_index == width_in_tiles) { + x_tile_index = 0; + } + } + } + // Compute the destination pixel in the buffer and mask based on the transformations. int16_t offset = row_start + (input_pixel.x - start_x + x_shift) * x_stride; // in pixels @@ -531,18 +646,20 @@ bool displayio_tilegrid_fill_area(displayio_tilegrid_t *self, if ((mask[offset / 32] & (1 << (offset % 32))) != 0) { continue; } - int16_t local_x = input_pixel.x / self->absolute_transform->scale; - uint16_t x_tile_index = (local_x / self->tile_width + self->top_left_x) % self->width_in_tiles; - uint16_t y_tile_index = (local_y / self->tile_height + self->top_left_y) % self->height_in_tiles; - uint16_t tile_location = y_tile_index * self->width_in_tiles + x_tile_index; + uint16_t tile_location = row_tile_location + x_tile_index; if (self->tiles_in_bitmap > 255) { input_pixel.tile = ((uint16_t *)tiles)[tile_location]; } else { input_pixel.tile = ((uint8_t *)tiles)[tile_location]; } - input_pixel.tile_x = (input_pixel.tile % self->bitmap_width_in_tiles) * self->tile_width + local_x % self->tile_width; - input_pixel.tile_y = (input_pixel.tile / self->bitmap_width_in_tiles) * self->tile_height + local_y % self->tile_height; + if (input_pixel.tile != cached_tile) { + cached_tile = input_pixel.tile; + tile_base_x = (input_pixel.tile % self->bitmap_width_in_tiles) * tile_width; + tile_base_y = (input_pixel.tile / self->bitmap_width_in_tiles) * self->tile_height; + } + input_pixel.tile_x = tile_base_x + x_in_tile; + input_pixel.tile_y = tile_base_y + y_in_tile; output_pixel.pixel = 0; input_pixel.pixel = 0; diff --git a/shared-module/displayio/__init__.c b/shared-module/displayio/__init__.c index c1309ee0972..c06d6d9cd60 100644 --- a/shared-module/displayio/__init__.c +++ b/shared-module/displayio/__init__.c @@ -72,7 +72,7 @@ displayio_buffer_transform_t null_transform = { .transpose_xy = false }; -#if CIRCUITPY_RGBMATRIX || CIRCUITPY_IS31FL3741 || CIRCUITPY_VIDEOCORE || CIRCUITPY_PICODVI || CIRCUITPY_MIPIDSI +#if CIRCUITPY_RGBMATRIX || CIRCUITPY_IS31FL3741 || CIRCUITPY_PICODVI || CIRCUITPY_MIPIDSI static bool any_display_uses_this_framebuffer(mp_obj_base_t *obj) { for (uint8_t i = 0; i < CIRCUITPY_DISPLAY_LIMIT; i++) { if (displays[i].display_base.type == &framebufferio_framebufferdisplay_type) { @@ -191,10 +191,6 @@ static void common_hal_displayio_release_displays_impl(bool keep_primary) { } else if (bus_type == &sharpdisplay_framebuffer_type) { common_hal_sharpdisplay_framebuffer_deinit(&display_buses[i].sharpdisplay); #endif - #if CIRCUITPY_VIDEOCORE - } else if (bus_type == &videocore_framebuffer_type) { - common_hal_videocore_framebuffer_deinit(&display_buses[i].videocore); - #endif #if CIRCUITPY_PICODVI } else if (bus_type == &picodvi_framebuffer_type) { common_hal_picodvi_framebuffer_deinit(&display_buses[i].picodvi); @@ -316,15 +312,6 @@ void reset_displays(void) { sharpdisplay_framebuffer_obj_t *sharp = &display_buses[i].sharpdisplay; common_hal_sharpdisplay_framebuffer_reset(sharp); #endif - #if CIRCUITPY_VIDEOCORE - } else if (display_bus_type == &videocore_framebuffer_type) { - videocore_framebuffer_obj_t *vc = &display_buses[i].videocore; - if (!any_display_uses_this_framebuffer(&vc->base)) { - common_hal_videocore_framebuffer_deinit(vc); - } - // The framebuffer is allocated outside of the heap so it doesn't - // need to be moved. - #endif #if CIRCUITPY_PICODVI } else if (display_bus_type == &picodvi_framebuffer_type) { picodvi_framebuffer_obj_t *vc = &display_buses[i].picodvi; diff --git a/shared-module/displayio/__init__.h b/shared-module/displayio/__init__.h index eabff662782..4674bda582d 100644 --- a/shared-module/displayio/__init__.h +++ b/shared-module/displayio/__init__.h @@ -48,9 +48,6 @@ #include "bindings/zephyr_display/Display.h" #endif // Port unique frame buffers. -#if CIRCUITPY_VIDEOCORE -#include "bindings/videocore/Framebuffer.h" -#endif #if CIRCUITPY_PICODVI #include "bindings/picodvi/Framebuffer.h" #endif @@ -78,9 +75,6 @@ typedef struct { #if CIRCUITPY_SHARPDISPLAY sharpdisplay_framebuffer_obj_t sharpdisplay; #endif - #if CIRCUITPY_VIDEOCORE - videocore_framebuffer_obj_t videocore; - #endif #if CIRCUITPY_PICODVI picodvi_framebuffer_obj_t picodvi; #endif diff --git a/shared-module/displayio/bus_core.c b/shared-module/displayio/bus_core.c index 9b3fb426217..2a3bcedf610 100644 --- a/shared-module/displayio/bus_core.c +++ b/shared-module/displayio/bus_core.c @@ -53,6 +53,9 @@ void displayio_display_bus_construct(displayio_display_bus_t *self, self->address_little_endian = address_little_endian; self->flush = NULL; + #if CIRCUITPY_BUSIO_SPI_ASYNC + self->send_async = NULL; + #endif #if CIRCUITPY_PARALLELDISPLAYBUS if (mp_obj_is_type(bus, ¶lleldisplaybus_parallelbus_type)) { @@ -72,6 +75,10 @@ void displayio_display_bus_construct(displayio_display_bus_t *self, self->send = common_hal_fourwire_fourwire_send; self->end_transaction = common_hal_fourwire_fourwire_end_transaction; self->collect_ptrs = common_hal_fourwire_fourwire_collect_ptrs; + #if CIRCUITPY_BUSIO_SPI_ASYNC + self->send_async = common_hal_fourwire_fourwire_send_async; + self->flush = common_hal_fourwire_fourwire_flush; + #endif } else #endif #if CIRCUITPY_I2CDISPLAYBUS diff --git a/shared-module/displayio/bus_core.h b/shared-module/displayio/bus_core.h index 75d03d7f284..fa23df97323 100644 --- a/shared-module/displayio/bus_core.h +++ b/shared-module/displayio/bus_core.h @@ -20,6 +20,10 @@ typedef struct { display_bus_bus_free bus_free; display_bus_begin_transaction begin_transaction; display_bus_send send; + // Optional. Like send, but may return before the data is sent: the data must not change and + // the bus must not be used until flush returns. NULL when the bus has no such mode; only set + // and read when CIRCUITPY_BUSIO_SPI_ASYNC. + display_bus_send send_async; display_bus_end_transaction end_transaction; display_bus_flush flush; display_bus_collect_ptrs collect_ptrs; diff --git a/shared-module/emmcio/__init__.c b/shared-module/emmcio/__init__.c index 075451a6ebc..3eaab720ea6 100644 --- a/shared-module/emmcio/__init__.c +++ b/shared-module/emmcio/__init__.c @@ -87,8 +87,8 @@ void automount_emmc(void) { } // Same as CIRCUITPY: while a host has the drive, the host owns writing. - filesystem_set_concurrent_write_protection(vfs, true); - filesystem_set_writable_by_usb(vfs, true); + filesystem_set_concurrent_write_protection((supervisor_vfs_t *)vfs, true); + filesystem_set_writable_by_usb((supervisor_vfs_t *)vfs, true); mp_vfs_mount_t *emmc_vfs = &_emmc_vfs; emmc_vfs->str = CIRCUITPY_EMMC_MOUNT_PATH; diff --git a/shared-module/epaperdisplay/EPaperDisplay.c b/shared-module/epaperdisplay/EPaperDisplay.c index d34be9d5c7c..3b3908d1fb2 100644 --- a/shared-module/epaperdisplay/EPaperDisplay.c +++ b/shared-module/epaperdisplay/EPaperDisplay.c @@ -88,7 +88,9 @@ void common_hal_epaperdisplay_epaperdisplay_construct(epaperdisplay_epaperdispla if (args->busy_pin != NULL) { self->busy.base.type = &digitalio_digitalinout_type; common_hal_digitalio_digitalinout_construct(&self->busy, args->busy_pin); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(args->busy_pin); + #endif } // Clear the color memory if it isn't in use. diff --git a/shared-module/fourwire/FourWire.c b/shared-module/fourwire/FourWire.c index d87cb0cec0f..f475b73ac9d 100644 --- a/shared-module/fourwire/FourWire.c +++ b/shared-module/fourwire/FourWire.c @@ -23,7 +23,9 @@ void common_hal_fourwire_fourwire_construct(fourwire_fourwire_obj_t *self, uint8_t polarity, uint8_t phase) { self->bus = spi; + #if CIRCUITPY_BULK_RESET common_hal_busio_spi_never_reset(self->bus); + #endif self->frequency = baudrate; self->polarity = polarity; @@ -35,20 +37,26 @@ void common_hal_fourwire_fourwire_construct(fourwire_fourwire_obj_t *self, self->command = digitalinout_protocol_from_pin(command, MP_QSTR_command, true, use_port_allocation, &self->own_command); if (self->command != mp_const_none) { digitalinout_protocol_switch_to_output(self->command, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(command); + #endif } self->reset = digitalinout_protocol_from_pin(reset, MP_QSTR_reset, true, use_port_allocation, &self->own_reset); if (self->reset != mp_const_none) { digitalinout_protocol_switch_to_output(self->reset, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(reset); + #endif common_hal_fourwire_fourwire_reset(self); } self->chip_select = digitalinout_protocol_from_pin(chip_select, MP_QSTR_chip_select, true, use_port_allocation, &self->own_chip_select); if (self->chip_select != mp_const_none) { digitalinout_protocol_switch_to_output(self->chip_select, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(chip_select); + #endif } } @@ -178,6 +186,29 @@ void common_hal_fourwire_fourwire_send(mp_obj_t obj, display_byte_type_t data_ty } } +#if CIRCUITPY_BUSIO_SPI_ASYNC +void common_hal_fourwire_fourwire_send_async(mp_obj_t obj, display_byte_type_t data_type, + display_chip_select_behavior_t chip_select, const uint8_t *data, uint32_t data_length) { + fourwire_fourwire_obj_t *self = MP_OBJ_TO_PTR(obj); + // Without a DC pin every byte carries a DC bit, and the toggling mode pulses CS per byte: + // both are sent byte by byte, so they stay synchronous. + if (self->command == mp_const_none || chip_select == CHIP_SELECT_TOGGLE_EVERY_BYTE) { + common_hal_fourwire_fourwire_send(obj, data_type, chip_select, data, data_length); + return; + } + if (data_length == 0) { + return; + } + digitalinout_protocol_set_value(self->command, data_type == DISPLAY_DATA); + common_hal_busio_spi_write_start(self->bus, data, data_length, &self->sent); +} + +void common_hal_fourwire_fourwire_flush(mp_obj_t obj) { + fourwire_fourwire_obj_t *self = MP_OBJ_TO_PTR(obj); + common_hal_busio_spi_end(self->bus); +} +#endif + void common_hal_fourwire_fourwire_end_transaction(mp_obj_t obj) { fourwire_fourwire_obj_t *self = MP_OBJ_TO_PTR(obj); if (self->chip_select != mp_const_none) { diff --git a/shared-module/fourwire/FourWire.h b/shared-module/fourwire/FourWire.h index c239a3dfde3..1abf74fcadc 100644 --- a/shared-module/fourwire/FourWire.h +++ b/shared-module/fourwire/FourWire.h @@ -25,4 +25,7 @@ typedef struct { bool own_command; bool own_chip_select; bool own_reset; + #if CIRCUITPY_BUSIO_SPI_ASYNC + circuitpy_async_flag_t sent; // set once the last send_async has been sent + #endif } fourwire_fourwire_obj_t; diff --git a/shared-module/getpass/__init__.c b/shared-module/getpass/__init__.c index 5263ff637d6..446e1b1d280 100644 --- a/shared-module/getpass/__init__.c +++ b/shared-module/getpass/__init__.c @@ -15,7 +15,7 @@ mp_obj_t shared_module_getpass_getpass(const char *prompt, mp_print_t *print) { if (print == NULL) { mp_hal_stdout_tx_str(prompt); } else { - mp_printf(print, prompt); + mp_print_str(print, prompt); } for (;;) { diff --git a/shared-module/gifio/GifWriter.c b/shared-module/gifio/GifWriter.c index bf0b33836cf..d875a844d9f 100644 --- a/shared-module/gifio/GifWriter.c +++ b/shared-module/gifio/GifWriter.c @@ -18,6 +18,12 @@ #define BLOCK_SIZE (126) // (2^7) - 2 // (DO NOT CHANGE!) +#define GIF_HEADER_SIZE (13 + 128 * 3 + 19) +// Each frame writes a 19 byte header ahead of the block data (8 for the +// graphic control extension, 11 for the image descriptor) and a 3 byte end +// code after it. +#define GIF_FRAME_OVERHEAD (19 + 3) + static void handle_error(gifio_gifwriter_t *self) { if (self->error != 0) { mp_raise_OSError(self->error); @@ -68,8 +74,8 @@ void shared_module_gifio_gifwriter_construct(gifio_gifwriter_t *self, mp_obj_t * self->dither = dither; self->own_file = own_file; - size_t nblocks = (width * height + 125) / 126; - self->size = nblocks * 128 + 4; + size_t nblocks = (width * height + BLOCK_SIZE - 1) / BLOCK_SIZE; + self->size = MAX(GIF_HEADER_SIZE, nblocks * 128 + GIF_FRAME_OVERHEAD); self->data = m_malloc_without_collect(self->size); self->cur = 0; self->error = 0; @@ -155,6 +161,15 @@ static const uint8_t g_bayer[4][4] = { }; void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_buffer_info_t *bufinfo, int16_t delay) { + int pixel_count = self->width * self->height; + int blocks = (pixel_count + BLOCK_SIZE - 1) / BLOCK_SIZE; + + size_t needed_bytes = (self->colorspace == DISPLAYIO_COLORSPACE_L8) + ? (size_t)pixel_count + : 2u * (size_t)pixel_count; + mp_get_index(&mp_type_memoryview, bufinfo->len, + MP_OBJ_NEW_SMALL_INT(needed_bytes - 1), false); + if (delay) { write_data(self, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); write_word(self, delay); @@ -167,13 +182,9 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b write_word(self, self->height); write_data(self, (uint8_t []) {0x00, 0x07}, 2); // 7-bits - int pixel_count = self->width * self->height; - int blocks = (pixel_count + BLOCK_SIZE - 1) / BLOCK_SIZE; - uint8_t *data = self->data + self->cur; if (self->colorspace == DISPLAYIO_COLORSPACE_L8) { - mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(pixel_count - 1), false); uint8_t *pixels = bufinfo->buf; for (int i = 0; i < blocks; i++) { @@ -187,7 +198,6 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b } } } else if (!self->dither) { - mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(2 * pixel_count - 1), false); uint16_t *pixels = bufinfo->buf; for (int i = 0; i < blocks; i++) { @@ -208,7 +218,6 @@ void shared_module_gifio_gifwriter_add_frame(gifio_gifwriter_t *self, const mp_b } } } else { - mp_get_index(&mp_type_memoryview, bufinfo->len, MP_OBJ_NEW_SMALL_INT(2 * pixel_count - 1), false); uint16_t *pixels = bufinfo->buf; int x = 0, y = 0; diff --git a/shared-module/gifio/OnDiskGif.c b/shared-module/gifio/OnDiskGif.c index 2b95e875c71..78ec60f3a99 100644 --- a/shared-module/gifio/OnDiskGif.c +++ b/shared-module/gifio/OnDiskGif.c @@ -12,12 +12,11 @@ #include "py/mperrno.h" #include "py/runtime.h" +#include "py/stream.h" static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { - uint32_t iBytesRead; - iBytesRead = iLen; - pyb_file_obj_t *f = pFile->fHandle; + uint32_t iBytesRead = iLen; // Note: If you read a file all the way to the last byte, seek() stops working if ((pFile->iSize - pFile->iPos) < iLen) { iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around @@ -25,21 +24,25 @@ static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { if (iBytesRead <= 0) { return 0; } - UINT bytes_read; - if (f_read(&f->fp, pBuf, iBytesRead, &bytes_read) != FR_OK) { - mp_raise_OSError(MP_EIO); + int errcode = 0; + mp_uint_t bytes_read = mp_stream_rw(MP_OBJ_FROM_PTR(pFile->fHandle), pBuf, + iBytesRead, &errcode, MP_STREAM_RW_ONCE); + if (errcode != 0) { + mp_raise_OSError(errcode); } - pFile->iPos = f->fp.fptr; + pFile->iPos += bytes_read; return bytes_read; } /* GIFReadFile() */ static int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) { - pyb_file_obj_t *f = pFile->fHandle; - - f_lseek(&f->fp, iPosition); - pFile->iPos = f->fp.fptr; - return pFile->iPos; + int errcode = 0; + mp_off_t pos = mp_stream_seek(MP_OBJ_FROM_PTR(pFile->fHandle), iPosition, MP_SEEK_SET, &errcode); + if (pos < 0) { + mp_raise_OSError(errcode); + } + pFile->iPos = pos; + return pos; } /* GIFSeekFile() */ static void GIFDraw(GIFDRAW *pDraw) { @@ -127,7 +130,7 @@ static void GIFDraw(GIFDRAW *pDraw) { } } -void common_hal_gifio_ondiskgif_construct(gifio_ondiskgif_t *self, pyb_file_obj_t *file, bool use_palette) { +void common_hal_gifio_ondiskgif_construct(gifio_ondiskgif_t *self, mp_obj_t file, bool use_palette) { self->file = file; if (use_palette == true) { @@ -142,10 +145,22 @@ void common_hal_gifio_ondiskgif_construct(gifio_ondiskgif_t *self, pyb_file_obj_ self->gif.pfnDraw = GIFDraw; self->gif.pfnClose = NULL; self->gif.pfnOpen = NULL; - self->gif.GIFFile.fHandle = self->file; + self->gif.GIFFile.fHandle = MP_OBJ_TO_PTR(self->file); - f_rewind(&self->file->fp); - self->gif.GIFFile.iSize = (int32_t)f_size(&self->file->fp); + int errcode = 0; + mp_stream_seek(self->file, 0, MP_SEEK_SET, &errcode); + if (errcode != 0) { + mp_raise_OSError(errcode); + } + mp_off_t size = mp_stream_seek(self->file, 0, MP_SEEK_END, &errcode); + if (size < 0) { + mp_raise_OSError(errcode); + } + mp_stream_seek(self->file, 0, MP_SEEK_SET, &errcode); + if (errcode != 0) { + mp_raise_OSError(errcode); + } + self->gif.GIFFile.iSize = (int32_t)size; int result = GIF_init(&self->gif); if (result != 1) { diff --git a/shared-module/gifio/OnDiskGif.h b/shared-module/gifio/OnDiskGif.h index ee9c40ad33a..808b6baa89f 100644 --- a/shared-module/gifio/OnDiskGif.h +++ b/shared-module/gifio/OnDiskGif.h @@ -15,12 +15,10 @@ #include "shared-module/displayio/Bitmap.h" #include "shared-module/displayio/Palette.h" -#include "extmod/vfs_fat.h" - typedef struct { mp_obj_base_t base; GIFIMAGE gif; - pyb_file_obj_t *file; + mp_obj_t file; displayio_bitmap_t *bitmap; displayio_palette_t *palette; int32_t duration; diff --git a/shared-module/hardwarekey/HardwareKey.c b/shared-module/hardwarekey/HardwareKey.c new file mode 100644 index 00000000000..8ce6a418d00 --- /dev/null +++ b/shared-module/hardwarekey/HardwareKey.c @@ -0,0 +1,27 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#include "shared-module/hardwarekey/HardwareKey.h" + +// These accessors are port-independent. The one port-specific step -- turning a +// hardware key slot into self->key_id -- happens when the port fills in its +// per-slot HardwareKey objects at startup. + +mp_int_t common_hal_hardwarekey_hardwarekey_get_key_slot(hardwarekey_hardwarekey_obj_t *self) { + return self->key_slot; +} + +hardwarekey_purpose_t common_hal_hardwarekey_hardwarekey_get_purpose(hardwarekey_hardwarekey_obj_t *self) { + return self->purpose; +} + +bool common_hal_hardwarekey_hardwarekey_get_exportable(hardwarekey_hardwarekey_obj_t *self) { + return self->exportable; +} + +psa_key_id_t common_hal_hardwarekey_hardwarekey_get_key_id(hardwarekey_hardwarekey_obj_t *self) { + return self->key_id; +} diff --git a/shared-module/hardwarekey/HardwareKey.h b/shared-module/hardwarekey/HardwareKey.h new file mode 100644 index 00000000000..375cb354513 --- /dev/null +++ b/shared-module/hardwarekey/HardwareKey.h @@ -0,0 +1,46 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include "py/obj.h" + +#include "psa/crypto.h" + +// What a hardware key slot is provisioned for. UNUSED means no key has been +// burned into the slot (or it is burned for something this module does not +// expose); the slot is present in `board` but not usable. +typedef enum { + HARDWAREKEY_PURPOSE_UNUSED = 0, + HARDWAREKEY_PURPOSE_HMAC, +} hardwarekey_purpose_t; + +// The handle is portable: it holds a PSA key id. How that id gets created -- +// which hardware key store, which slot -- is the one port-specific step, done +// when the port populates its per-slot HardwareKey objects at startup. +typedef struct { + mp_obj_base_t base; + psa_key_id_t key_id; + mp_int_t key_slot; + hardwarekey_purpose_t purpose; + bool exportable; + // Name this key is exposed under in `board` (e.g. MP_QSTR_EFUSE_KEY0), for + // repr(). MP_QSTRnull if the object was not placed in `board`. + qstr name; +} hardwarekey_hardwarekey_obj_t; + +// HardwareKey objects are created by the port at startup, one per hardware key +// slot, and placed in `board`; application code never constructs them. The +// per-port startup code fills in key_id, key_slot, purpose, exportable and name. +// The key is used by passing the object to hmac.new(). + +mp_int_t common_hal_hardwarekey_hardwarekey_get_key_slot(hardwarekey_hardwarekey_obj_t *self); +hardwarekey_purpose_t common_hal_hardwarekey_hardwarekey_get_purpose(hardwarekey_hardwarekey_obj_t *self); +bool common_hal_hardwarekey_hardwarekey_get_exportable(hardwarekey_hardwarekey_obj_t *self); +// The PSA key id, for hmac.new(). 0 if the slot is unused. +psa_key_id_t common_hal_hardwarekey_hardwarekey_get_key_id(hardwarekey_hardwarekey_obj_t *self); diff --git a/shared-module/hmac/HMAC.c b/shared-module/hmac/HMAC.c new file mode 100644 index 00000000000..20b89e0a16c --- /dev/null +++ b/shared-module/hmac/HMAC.c @@ -0,0 +1,125 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/runtime.h" + +#include "shared-bindings/hmac/HMAC.h" +#include "shared-module/hmac/__init__.h" + +#include "psa/crypto.h" + +#define HMAC_ALG(self) (PSA_ALG_HMAC((self)->hash_alg)) + +// On failure, resets mac_op (per the PSA multipart contract: an operation +// that errors out must be aborted before it can be discarded) and raises. +static void check_psa(hmac_hmac_obj_t *self, psa_status_t status) { + if (status != PSA_SUCCESS) { + psa_mac_abort(&self->mac_op); + mp_raise_RuntimeError(NULL); + } +} + +void common_hal_hmac_new(hmac_hmac_obj_t *self, const uint8_t *key, size_t key_len, + psa_key_id_t borrowed_key_id, psa_algorithm_t hash_alg) { + self->hash_alg = hash_alg; + self->finished = false; + self->digest_len = 0; + self->mac_op = psa_mac_operation_init(); + self->buffered = (borrowed_key_id != 0); + if (self->buffered) { + vstr_init(&self->buf, 0); + } + + if (psa_crypto_init() != PSA_SUCCESS) { + mp_raise_RuntimeError(NULL); + } + + if (borrowed_key_id != 0) { + self->key_id = borrowed_key_id; + self->owns_key = false; + } else { + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&attr, PSA_KEY_TYPE_HMAC); + psa_set_key_bits(&attr, key_len * 8); + psa_set_key_algorithm(&attr, HMAC_ALG(self)); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_VOLATILE); + if (psa_import_key(&attr, key, key_len, &self->key_id) != PSA_SUCCESS) { + mp_raise_RuntimeError(NULL); + } + self->owns_key = true; + } + + psa_status_t status = psa_mac_sign_setup(&self->mac_op, self->key_id, HMAC_ALG(self)); + if (status != PSA_SUCCESS) { + if (self->owns_key) { + psa_destroy_key(self->key_id); + self->owns_key = false; + } + if (status == PSA_ERROR_NOT_PERMITTED) { + // A hardware-backed key can be locked to one digest algorithm -- + // a common restriction for this kind of peripheral. + mp_raise_ValueError(MP_ERROR_TEXT("key does not support this digest")); + } + mp_raise_RuntimeError(NULL); + } +} + +void common_hal_hmac_update(hmac_hmac_obj_t *self, const uint8_t *data, size_t data_len) { + if (self->finished) { + mp_raise_RuntimeError(NULL); + } + if (self->buffered) { + vstr_add_strn(&self->buf, (const char *)data, data_len); + return; + } + check_psa(self, psa_mac_update(&self->mac_op, data, data_len)); +} + +void common_hal_hmac_digest(hmac_hmac_obj_t *self, uint8_t *out, size_t out_len) { + if (!self->finished) { + if (self->buffered) { + if (self->buf.len == 0) { + // Some hardware-backed keys' PSA drivers reject a + // zero-length psa_mac_update() outright (and skipping the + // call entirely would leave the result at all-zero bytes + // from setup -- a wrong answer, not an error), so an empty + // message can't be computed through this path. + psa_mac_abort(&self->mac_op); + mp_raise_ValueError(MP_ERROR_TEXT("HMAC of an empty message is not supported with a hardware key")); + } + check_psa(self, psa_mac_update(&self->mac_op, (const uint8_t *)self->buf.buf, self->buf.len)); + } + psa_status_t status = psa_mac_sign_finish(&self->mac_op, self->digest, sizeof(self->digest), + &self->digest_len); + // The operation is spent either way -- successful finish or not, it + // can't be resumed, so the owned key's job is done too. + if (self->owns_key) { + psa_destroy_key(self->key_id); + self->owns_key = false; + } + check_psa(self, status); + self->finished = true; + } + memcpy(out, self->digest, out_len); +} + +size_t common_hal_hmac_get_digest_size(hmac_hmac_obj_t *self) { + return PSA_HASH_LENGTH(self->hash_alg); +} + +size_t common_hal_hmac_get_block_size(hmac_hmac_obj_t *self) { + return PSA_HASH_BLOCK_LENGTH(self->hash_alg); +} + +const char *common_hal_hmac_get_name(hmac_hmac_obj_t *self) { + if (self->hash_alg == PSA_ALG_SHA_1) { + return "hmac-sha1"; + } + return "hmac-sha256"; +} diff --git a/shared-module/hmac/__init__.c b/shared-module/hmac/__init__.c new file mode 100644 index 00000000000..7d3670dfcc7 --- /dev/null +++ b/shared-module/hmac/__init__.c @@ -0,0 +1,42 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#include + +#include "mbedtls/constant_time.h" +#include "shared-module/hmac/__init__.h" + +bool hmac_hash_alg_from_name(const char *name, psa_algorithm_t *hash_alg) { + if (strcmp(name, "sha256") == 0) { + *hash_alg = PSA_ALG_SHA_256; + } else if (strcmp(name, "sha1") == 0) { + *hash_alg = PSA_ALG_SHA_1; + } else { + return false; + } + return true; +} + +bool common_hal_hmac_compare_digest(const uint8_t *a, size_t a_len, const uint8_t *b, size_t b_len) { + // Same shape as CPython's _tscmp: the running time depends only on len(a), + // never on where (or whether) the two inputs first differ. The byte + // comparison itself is mbedtls_ct_memcmp(), which is hardened (volatile + // accesses, no early-exit branch) against being optimized into a + // variable-time comparison. + const uint8_t *right = b; + int mismatch = 0; + + if (a_len != b_len) { + // Compare a against itself so the call still does a_len bytes of work, + // then force a mismatch. + right = a; + mismatch = 1; + } + + mismatch |= mbedtls_ct_memcmp(a, right, a_len); + + return mismatch == 0; +} diff --git a/shared-module/hmac/__init__.h b/shared-module/hmac/__init__.h new file mode 100644 index 00000000000..677ed0aab81 --- /dev/null +++ b/shared-module/hmac/__init__.h @@ -0,0 +1,55 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Mabey +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +#include "py/misc.h" +#include "py/obj.h" + +#include "psa/crypto.h" + +typedef struct { + mp_obj_base_t base; + // The digest algorithm the HMAC is built on, e.g. PSA_ALG_SHA_256. + psa_algorithm_t hash_alg; + // The PSA multipart MAC operation. update() normally streams straight + // into this (see `buffered` below for the exception); PSA has no + // psa_mac_clone(), so unlike shared-module/hashlib's Hash this can't be + // rewound -- digest() finishes it exactly once and caches the result. + psa_mac_operation_t mac_op; + // The PSA key used by mac_op. For a bytes key, imported at construction + // time and destroyed once mac_op is finished (owns_key true). For a key + // borrowed from a hardwarekey.HardwareKey, that object owns the key and + // owns_key is false. + psa_key_id_t key_id; + bool owns_key; + // True for a borrowed hardware key whose PSA driver doesn't implement + // real multipart MAC support: some hardware-backed HMAC peripherals only + // expose a one-shot compute primitive, so a PSA driver built on one may + // independently compute the HMAC of just each update() call's bytes and + // discard prior state -- silently giving the wrong answer if update() is + // called more than once. When true, update() accumulates into `buf` + // instead and digest() feeds it to mac_op in one call. Bytes keys stream + // normally -- this only affects the (typically short) hardware-key path. + bool buffered; + vstr_t buf; + // Set once digest()/hexdigest() has finished mac_op. update() raises + // after this; further digest() calls just return the cached bytes. + bool finished; + uint8_t digest[PSA_HASH_MAX_SIZE]; + size_t digest_len; +} hmac_hmac_obj_t; + +// Maps a CPython digest name ("sha1", "sha256") to a PSA hash algorithm. +// Returns false for an unsupported name. +bool hmac_hash_alg_from_name(const char *name, psa_algorithm_t *hash_alg); + +// Constant-time equality, matching hmac.compare_digest() / CPython's _tscmp. +bool common_hal_hmac_compare_digest(const uint8_t *a, size_t a_len, const uint8_t *b, size_t b_len); diff --git a/shared-module/i2cdisplaybus/I2CDisplayBus.c b/shared-module/i2cdisplaybus/I2CDisplayBus.c index 47b5865d158..cf276c1fb88 100644 --- a/shared-module/i2cdisplaybus/I2CDisplayBus.c +++ b/shared-module/i2cdisplaybus/I2CDisplayBus.c @@ -27,7 +27,9 @@ void common_hal_i2cdisplaybus_i2cdisplaybus_construct(i2cdisplaybus_i2cdisplaybu self->reset.base.type = &digitalio_digitalinout_type; common_hal_digitalio_digitalinout_construct(&self->reset, reset); common_hal_digitalio_digitalinout_switch_to_output(&self->reset, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(reset); + #endif common_hal_i2cdisplaybus_i2cdisplaybus_reset(self); } @@ -40,7 +42,9 @@ void common_hal_i2cdisplaybus_i2cdisplaybus_construct(i2cdisplaybus_i2cdisplaybu // Write to the device and return 0 on success or an appropriate error code from mperrno.h self->bus = i2c; + #if CIRCUITPY_BULK_RESET common_hal_busio_i2c_never_reset(self->bus); + #endif self->address = device_address; } diff --git a/shared-module/is31fl3741/FrameBuffer.c b/shared-module/is31fl3741/FrameBuffer.c index 7f4660d857c..610f330334b 100644 --- a/shared-module/is31fl3741/FrameBuffer.c +++ b/shared-module/is31fl3741/FrameBuffer.c @@ -27,7 +27,9 @@ void common_hal_is31fl3741_framebuffer_construct(is31fl3741_framebuffer_obj_t *s self->is31fl3741 = is31; + #if CIRCUITPY_BULK_RESET common_hal_busio_i2c_never_reset(self->is31fl3741->i2c); + #endif mp_obj_t *items; size_t len; diff --git a/shared-module/jpegio/JpegDecoder.c b/shared-module/jpegio/JpegDecoder.c index 89cb624a8d0..b70cfccf22d 100644 --- a/shared-module/jpegio/JpegDecoder.c +++ b/shared-module/jpegio/JpegDecoder.c @@ -89,7 +89,7 @@ static size_t file_input(JDEC *jd, uint8_t *dest, size_t len) { read = file_input(jd, buf, to_discard); total += read; } while (read != 0 && total != len); - return len; + return total; } int errcode = 0; @@ -147,14 +147,14 @@ static int bitmap_output(JDEC *jd, void *data, JRECT *rect) { int y1 = self->lim.y1 - rect->top; int y2 = self->lim.y2 - rect->top; - if (y2 < y1) { + if (y2 <= 0) { // The last row in the source image to copy FROM is above of this, so // no more pixels on any rows return DECODER_INTERRUPT; } y2 = MIN(y2, src_height); - if (x2 < x1) { + if (x2 <= 0) { // The last column in the source image to copy FROM is left of this, so // no more pixels on this row but could be on subsequent rows return DECODER_CONTINUE; diff --git a/shared-module/keypad/__init__.c b/shared-module/keypad/__init__.c index f1d5d395df7..54ebffdc7eb 100644 --- a/shared-module/keypad/__init__.c +++ b/shared-module/keypad/__init__.c @@ -139,9 +139,11 @@ bool keypad_debounce(keypad_scanner_obj_t *self, mp_uint_t key_number, bool curr return false; } +#if CIRCUITPY_BULK_RESET void keypad_never_reset(keypad_scanner_obj_t *self) { self->never_reset = true; } +#endif void common_hal_keypad_generic_reset(void *self_in) { keypad_scanner_obj_t *self = self_in; diff --git a/shared-module/keypad/__init__.h b/shared-module/keypad/__init__.h index 66fea487854..8ae11e40b10 100644 --- a/shared-module/keypad/__init__.h +++ b/shared-module/keypad/__init__.h @@ -41,7 +41,9 @@ void keypad_register_scanner(keypad_scanner_obj_t *scanner); void keypad_deregister_scanner(keypad_scanner_obj_t *scanner); void keypad_construct_common(keypad_scanner_obj_t *scanner, mp_float_t interval, size_t max_events, uint8_t debounce_cycles, bool use_gc_allocator); bool keypad_debounce(keypad_scanner_obj_t *self, mp_uint_t key_number, bool current); +#if CIRCUITPY_BULK_RESET void keypad_never_reset(keypad_scanner_obj_t *self); +#endif size_t common_hal_keypad_generic_get_key_count(void *scanner); void common_hal_keypad_deinit_core(void *scanner); diff --git a/shared-module/keypad_demux/DemuxKeyMatrix.c b/shared-module/keypad_demux/DemuxKeyMatrix.c index 329f7679d3f..b2f4bddbaec 100644 --- a/shared-module/keypad_demux/DemuxKeyMatrix.c +++ b/shared-module/keypad_demux/DemuxKeyMatrix.c @@ -162,6 +162,7 @@ static void demuxkeymatrix_scan_now(void *self_in, mp_obj_t timestamp) { } } +#if CIRCUITPY_BULK_RESET void demuxkeymatrix_never_reset(keypad_demux_demuxkeymatrix_obj_t *self) { keypad_never_reset((keypad_scanner_obj_t *)self); for (size_t row_addr = 0; row_addr < self->row_addr_digitalinouts->len; row_addr++) { @@ -171,3 +172,4 @@ void demuxkeymatrix_never_reset(keypad_demux_demuxkeymatrix_obj_t *self) { common_hal_digitalio_digitalinout_never_reset(self->column_digitalinouts->items[column]); } } +#endif diff --git a/shared-module/keypad_demux/DemuxKeyMatrix.h b/shared-module/keypad_demux/DemuxKeyMatrix.h index fe315775dd2..6f4d6ec32d4 100644 --- a/shared-module/keypad_demux/DemuxKeyMatrix.h +++ b/shared-module/keypad_demux/DemuxKeyMatrix.h @@ -22,4 +22,6 @@ typedef struct { } keypad_demux_demuxkeymatrix_obj_t; void keypad_demux_demuxkeymatrix_scan(keypad_demux_demuxkeymatrix_obj_t *self); +#if CIRCUITPY_BULK_RESET void demuxkeymatrix_never_reset(keypad_demux_demuxkeymatrix_obj_t *self); +#endif diff --git a/shared-module/lvfontio/OnDiskFont.c b/shared-module/lvfontio/OnDiskFont.c index efffed63074..91fe3b214b0 100644 --- a/shared-module/lvfontio/OnDiskFont.c +++ b/shared-module/lvfontio/OnDiskFont.c @@ -13,8 +13,6 @@ #include "py/objstr.h" #include "py/gc.h" #include "shared-bindings/displayio/Bitmap.h" -#include "extmod/vfs_fat.h" -#include "lib/oofatfs/ff.h" #include "supervisor/shared/translate/translate.h" #include "supervisor/port.h" #include "supervisor/shared/serial.h" @@ -50,17 +48,24 @@ static inline void free_memory(lvfontio_ondiskfont_t *self, void *ptr) { static int16_t find_codepoint_slot(lvfontio_ondiskfont_t *self, uint32_t codepoint); static bool slot_has_active_full_width_partner(lvfontio_ondiskfont_t *self, uint16_t slot); static uint16_t find_free_slot(lvfontio_ondiskfont_t *self, uint32_t codepoint); -static FRESULT read_bits(FIL *file, size_t num_bits, uint8_t *byte_val, uint8_t *remaining_bits, uint32_t *result); -static FRESULT read_glyph_dimensions(FIL *file, lvfontio_ondiskfont_t *self, uint32_t *advance_width, int32_t *bbox_x, int32_t *bbox_y, uint32_t *bbox_w, uint32_t *bbox_h, uint8_t *byte_val, uint8_t *remaining_bits); +static int16_t cache_glyph_locked(lvfontio_ondiskfont_t *self, uint32_t codepoint, bool *is_full_width); +static bool read_bits(supervisor_vfs_file_t *file, size_t num_bits, uint8_t *byte_val, uint8_t *remaining_bits, uint32_t *result); +static bool read_glyph_dimensions(supervisor_vfs_file_t *file, lvfontio_ondiskfont_t *self, uint32_t *advance_width, int32_t *bbox_x, int32_t *bbox_y, uint32_t *bbox_w, uint32_t *bbox_h, uint8_t *byte_val, uint8_t *remaining_bits); + +static bool read_file(supervisor_vfs_file_t *file, void *buf, size_t len) { + size_t bytes_read; + supervisor_fs_err_t err = supervisor_vfs_read_file(file, buf, len, &bytes_read); + return err == SUPERVISOR_FS_OK && bytes_read >= len; +} -// Load font header data from file -static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max_slots) { - UINT bytes_read; - FRESULT res; +static bool seek_file(supervisor_vfs_file_t *file, size_t offset) { + return supervisor_vfs_seek_file(file, offset) == SUPERVISOR_FS_OK; +} +// Load font header data from file +static bool load_font_header(lvfontio_ondiskfont_t *self, supervisor_vfs_file_t *file, size_t *max_slots) { // Start at the beginning of the file - res = f_lseek(file, 0); - if (res != FR_OK) { + if (!seek_file(file, 0)) { return false; } @@ -76,8 +81,7 @@ static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max // Read sections until we find all the sections we need or reach end of file while (true) { // Read section size (4 bytes) - res = f_read(file, buffer, 4, &bytes_read); - if (res != FR_OK || bytes_read < 4) { + if (!read_file(file, buffer, 4)) { break; // Read error or end of file } @@ -89,8 +93,7 @@ static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max } // Read section marker (4 bytes) - res = f_read(file, buffer, 4, &bytes_read); - if (res != FR_OK || bytes_read < 4) { + if (!read_file(file, buffer, 4)) { break; // Read error or unexpected end of file } @@ -103,8 +106,7 @@ static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max if (memcmp(buffer, "head", 4) == 0) { // Read head section data (35 bytes) uint8_t head_buf[35]; - res = f_read(file, head_buf, 35, &bytes_read); - if (res != FR_OK || bytes_read < 35) { + if (!read_file(file, head_buf, sizeof(head_buf))) { break; } @@ -131,8 +133,7 @@ static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max } else if (memcmp(buffer, "cmap", 4) == 0) { // Read subtable count uint8_t cmap_header[4]; - res = f_read(file, cmap_header, 4, &bytes_read); - if (res != FR_OK || bytes_read < 4) { + if (!read_file(file, cmap_header, sizeof(cmap_header))) { break; } @@ -149,8 +150,7 @@ static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max // Read each subtable for (uint16_t i = 0; i < subtable_count; i++) { uint8_t subtable_buf[16]; - res = f_read(file, subtable_buf, 16, &bytes_read); - if (res != FR_OK || bytes_read < 16) { + if (!read_file(file, subtable_buf, sizeof(subtable_buf))) { break; } @@ -185,8 +185,7 @@ static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max } else if (memcmp(buffer, "loca", 4) == 0) { // Read max_cid uint8_t loca_header[4]; - res = f_read(file, loca_header, 4, &bytes_read); - if (res != FR_OK || bytes_read < 4) { + if (!read_file(file, loca_header, sizeof(loca_header))) { break; } @@ -275,8 +274,7 @@ static bool load_font_header(lvfontio_ondiskfont_t *self, FIL *file, size_t *max current_position += section_size; // Skip to the end of the section - res = f_lseek(file, current_position); - if (res != FR_OK) { + if (!seek_file(file, current_position)) { break; } @@ -318,17 +316,13 @@ static int32_t get_char_id(lvfontio_ondiskfont_t *self, uint32_t codepoint) { // Calculate the absolute data position in the file uint32_t data_pos = self->cmap_ranges[i].data_offset + idx; // 1 byte per entry - FRESULT res = f_lseek(&self->file, data_pos); - if (res != FR_OK) { + if (!seek_file(&self->file, data_pos)) { return -1; } // Read the glyph ID (1 byte) uint8_t glyph_id; - UINT bytes_read; - res = f_read(&self->file, &glyph_id, 1, &bytes_read); - - if (res != FR_OK || bytes_read < 1) { + if (!read_file(&self->file, &glyph_id, 1)) { return -1; } @@ -346,9 +340,7 @@ static int32_t get_char_id(lvfontio_ondiskfont_t *self, uint32_t codepoint) { return -1; } - FRESULT res; - res = f_lseek(&self->file, self->cmap_ranges[i].data_offset); - if (res != FR_OK) { + if (!seek_file(&self->file, self->cmap_ranges[i].data_offset)) { return -1; } uint16_t codepoint_delta = codepoint - self->cmap_ranges[i].range_start; @@ -356,9 +348,7 @@ static int32_t get_char_id(lvfontio_ondiskfont_t *self, uint32_t codepoint) { for (size_t j = 0; j < self->cmap_ranges[i].entries_count; j++) { // Read code point at the index uint16_t candidate_codepoint_delta; - UINT bytes_read; - res = f_read(&self->file, &candidate_codepoint_delta, 2, &bytes_read); - if (res != FR_OK || bytes_read < 2) { + if (!read_file(&self->file, &candidate_codepoint_delta, 2)) { return -1; } @@ -380,7 +370,7 @@ static int32_t get_char_id(lvfontio_ondiskfont_t *self, uint32_t codepoint) { // Load glyph bitmap data into a slot // This function assumes the file is already open and positioned after reading the glyph dimensions -static bool load_glyph_bitmap(FIL *file, lvfontio_ondiskfont_t *self, uint32_t codepoint, uint16_t slot, +static bool load_glyph_bitmap(supervisor_vfs_file_t *file, lvfontio_ondiskfont_t *self, uint32_t codepoint, uint16_t slot, uint32_t glyph_advance, int32_t bbox_x, int32_t bbox_y, uint32_t bbox_w, uint32_t bbox_h, uint8_t *byte_val, uint8_t *remaining_bits) { // Store codepoint at slot @@ -393,8 +383,7 @@ static bool load_glyph_bitmap(FIL *file, lvfontio_ondiskfont_t *self, uint32_t c for (uint16_t y = 0; y < bbox_h; y++) { for (uint16_t x = 0; x < bbox_w; x++) { uint32_t pixel_value; - FRESULT res = read_bits(file, self->header.bits_per_pixel, byte_val, remaining_bits, &pixel_value); - if (res != FR_OK) { + if (!read_bits(file, self->header.bits_per_pixel, byte_val, remaining_bits, &pixel_value)) { return false; } @@ -433,22 +422,36 @@ void common_hal_lvfontio_ondiskfont_construct(lvfontio_ondiskfont_t *self, self->max_glyphs = max_glyphs; self->cmap_ranges = NULL; self->file_is_open = false; + // Initialize to NULL so an early deinit (e.g. header load failure) is safe. + self->bitmap = NULL; + self->codepoints = NULL; + self->reference_counts = NULL; // Determine which filesystem to use based on the path const char *path_under_mount; - fs_user_mount_t *vfs = filesystem_for_path(file_path, &path_under_mount); + supervisor_vfs_t *vfs = filesystem_for_path(file_path, &path_under_mount); - if (vfs == NULL) { + if (vfs == NULL || !supervisor_vfs_supported(vfs)) { + // Not found, or a filesystem the supervisor API can't access. if (self->use_gc_allocator) { mp_raise_ValueError(MP_ERROR_TEXT("File not found")); } return; } - // Open the file and keep it open for the lifetime of the object - FRESULT res = f_open(&vfs->fatfs, &self->file, path_under_mount, FA_READ); + // Take the filesystem lock while opening and parsing the header so USB + // MSC and other writers can't modify the blocks under us while reading. + if (!filesystem_lock(vfs)) { + if (self->use_gc_allocator) { + mp_raise_OSError(MP_EBUSY); + } + return; + } - if (res != FR_OK) { + // Open the file and keep it open for the lifetime of the object + supervisor_fs_err_t err = supervisor_vfs_open_file(vfs, path_under_mount, SUPERVISOR_FS_OPEN_READ, 0, &self->file); + if (err != SUPERVISOR_FS_OK) { + filesystem_unlock(vfs); if (self->use_gc_allocator) { mp_raise_ValueError(MP_ERROR_TEXT("File not found")); } @@ -456,17 +459,23 @@ void common_hal_lvfontio_ondiskfont_construct(lvfontio_ondiskfont_t *self, } self->file_is_open = true; + self->vfs = vfs; // Load font headers size_t max_slots; if (!load_font_header(self, &self->file, &max_slots)) { - f_close(&self->file); - self->file_is_open = false; + common_hal_lvfontio_ondiskfont_deinit(self); + filesystem_unlock(vfs); if (self->use_gc_allocator) { mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q"), MP_QSTR_file); } return; } + // All file reads are done; release the lock so other users (including + // other OnDiskFonts) may access the filesystem again until the next + // cache_glyph call. + filesystem_unlock(vfs); + // Cap the number of slots to the number of slots needed by the font. That way // small font files don't need a bunch of extra cache space. max_glyphs = MIN(max_glyphs, max_slots); @@ -531,7 +540,6 @@ void common_hal_lvfontio_ondiskfont_deinit(lvfontio_ondiskfont_t *self) { if (!self->file_is_open) { return; } - if (self->bitmap != NULL) { common_hal_displayio_bitmap_deinit(self->bitmap); self->bitmap = NULL; @@ -554,7 +562,7 @@ void common_hal_lvfontio_ondiskfont_deinit(lvfontio_ondiskfont_t *self) { self->cmap_ranges = NULL; } - f_close(&self->file); + supervisor_vfs_close_file(&self->file); self->file_is_open = false; } @@ -584,6 +592,17 @@ void common_hal_lvfontio_ondiskfont_get_dimensions(const lvfontio_ondiskfont_t * } int16_t common_hal_lvfontio_ondiskfont_cache_glyph(lvfontio_ondiskfont_t *self, uint32_t codepoint, bool *is_full_width) { + // Take the filesystem lock while reading glyph data so USB MSC and other + // writers can't modify the blocks under us mid-read. + if (!filesystem_lock(self->vfs)) { + return -1; + } + int16_t slot = cache_glyph_locked(self, codepoint, is_full_width); + filesystem_unlock(self->vfs); + return slot; +} + +static int16_t cache_glyph_locked(lvfontio_ondiskfont_t *self, uint32_t codepoint, bool *is_full_width) { // Check if already cached int16_t existing_slot = find_codepoint_slot(self, codepoint); if (existing_slot >= 0) { @@ -627,17 +646,14 @@ int16_t common_hal_lvfontio_ondiskfont_cache_glyph(lvfontio_ondiskfont_t *self, uint32_t loca_offset = self->loca_table_offset + char_id * (self->header.index_to_loc_format == 1 ? 4 : 2); - FRESULT res = f_lseek(&self->file, loca_offset); - if (res != FR_OK) { + if (!seek_file(&self->file, loca_offset)) { return -1; } - UINT bytes_read; if (self->header.index_to_loc_format == 1) { // 4-byte offset uint8_t offset_buf[4]; - res = f_read(&self->file, offset_buf, 4, &bytes_read); - if (res != FR_OK || bytes_read < 4) { + if (!read_file(&self->file, offset_buf, sizeof(offset_buf))) { return -1; } glyph_offset = offset_buf[0] | (offset_buf[1] << 8) | @@ -645,15 +661,13 @@ int16_t common_hal_lvfontio_ondiskfont_cache_glyph(lvfontio_ondiskfont_t *self, } else { // 2-byte offset uint8_t offset_buf[2]; - res = f_read(&self->file, offset_buf, 2, &bytes_read); - if (res != FR_OK || bytes_read < 2) { + if (!read_file(&self->file, offset_buf, sizeof(offset_buf))) { return -1; } glyph_offset = offset_buf[0] | (offset_buf[1] << 8); } // Seek to glyph data - res = f_lseek(&self->file, self->glyf_table_offset + glyph_offset); - if (res != FR_OK) { + if (!seek_file(&self->file, self->glyf_table_offset + glyph_offset)) { return -1; } @@ -667,8 +681,7 @@ int16_t common_hal_lvfontio_ondiskfont_cache_glyph(lvfontio_ondiskfont_t *self, uint8_t remaining_bits = 0; // Use the helper function to read glyph dimensions - res = read_glyph_dimensions(&self->file, self, &glyph_advance, &bbox_x, &bbox_y, &bbox_w, &bbox_h, &byte_val, &remaining_bits); - if (res != FR_OK) { + if (!read_glyph_dimensions(&self->file, self, &glyph_advance, &bbox_x, &bbox_y, &bbox_w, &bbox_h, &byte_val, &remaining_bits)) { return -1; } @@ -792,24 +805,21 @@ static uint16_t find_free_slot(lvfontio_ondiskfont_t *self, uint32_t codepoint) return UINT16_MAX; } -static FRESULT read_glyph_dimensions(FIL *file, lvfontio_ondiskfont_t *self, +static bool read_glyph_dimensions(supervisor_vfs_file_t *file, lvfontio_ondiskfont_t *self, uint32_t *advance_width, int32_t *bbox_x, int32_t *bbox_y, uint32_t *bbox_w, uint32_t *bbox_h, uint8_t *byte_val, uint8_t *remaining_bits) { - FRESULT res; uint32_t temp_value; // Read glyph_advance - res = read_bits(file, self->header.glyph_advance_bits, byte_val, remaining_bits, &temp_value); - if (res != FR_OK) { - return res; + if (!read_bits(file, self->header.glyph_advance_bits, byte_val, remaining_bits, &temp_value)) { + return false; } *advance_width = temp_value; // Read bbox_x (signed) - res = read_bits(file, self->header.glyph_bbox_xy_bits, byte_val, remaining_bits, &temp_value); - if (res != FR_OK) { - return res; + if (!read_bits(file, self->header.glyph_bbox_xy_bits, byte_val, remaining_bits, &temp_value)) { + return false; } // Convert to signed value if needed if (temp_value & (1 << (self->header.glyph_bbox_xy_bits - 1))) { @@ -819,9 +829,8 @@ static FRESULT read_glyph_dimensions(FIL *file, lvfontio_ondiskfont_t *self, } // Read bbox_y (signed) - res = read_bits(file, self->header.glyph_bbox_xy_bits, byte_val, remaining_bits, &temp_value); - if (res != FR_OK) { - return res; + if (!read_bits(file, self->header.glyph_bbox_xy_bits, byte_val, remaining_bits, &temp_value)) { + return false; } // Convert to signed value if needed if (temp_value & (1 << (self->header.glyph_bbox_xy_bits - 1))) { @@ -831,26 +840,21 @@ static FRESULT read_glyph_dimensions(FIL *file, lvfontio_ondiskfont_t *self, } // Read bbox_w - res = read_bits(file, self->header.glyph_bbox_wh_bits, byte_val, remaining_bits, &temp_value); - if (res != FR_OK) { - return res; + if (!read_bits(file, self->header.glyph_bbox_wh_bits, byte_val, remaining_bits, &temp_value)) { + return false; } *bbox_w = temp_value; // Read bbox_h - res = read_bits(file, self->header.glyph_bbox_wh_bits, byte_val, remaining_bits, &temp_value); - if (res != FR_OK) { - return res; + if (!read_bits(file, self->header.glyph_bbox_wh_bits, byte_val, remaining_bits, &temp_value)) { + return false; } *bbox_h = temp_value; - return FR_OK; + return true; } -static FRESULT read_bits(FIL *file, size_t num_bits, uint8_t *byte_val, uint8_t *remaining_bits, uint32_t *result) { - FRESULT res = FR_OK; - UINT bytes_read; - +static bool read_bits(supervisor_vfs_file_t *file, size_t num_bits, uint8_t *byte_val, uint8_t *remaining_bits, uint32_t *result) { uint32_t value = 0; // Bits will be lost when num_bits > 32. However, this is good for skipping bits. size_t bits_needed = num_bits; @@ -858,9 +862,8 @@ static FRESULT read_bits(FIL *file, size_t num_bits, uint8_t *byte_val, uint8_t while (bits_needed > 0) { // If no bits remaining, read a new byte if (*remaining_bits == 0) { - res = f_read(file, byte_val, 1, &bytes_read); - if (res != FR_OK || bytes_read < 1) { - return FR_DISK_ERR; + if (!read_file(file, byte_val, 1)) { + return false; } *remaining_bits = 8; } @@ -881,5 +884,5 @@ static FRESULT read_bits(FIL *file, size_t num_bits, uint8_t *byte_val, uint8_t if (result != NULL) { *result = value; } - return FR_OK; + return true; } diff --git a/shared-module/lvfontio/OnDiskFont.h b/shared-module/lvfontio/OnDiskFont.h index db68126e9f4..85076a8aa91 100644 --- a/shared-module/lvfontio/OnDiskFont.h +++ b/shared-module/lvfontio/OnDiskFont.h @@ -9,7 +9,7 @@ #include "py/obj.h" #include "shared-module/displayio/Bitmap.h" -#include "lib/oofatfs/ff.h" +#include "supervisor/filesystem.h" #define LVFONTIO_INVALID_CODEPOINT 0xFFFFFFFF @@ -58,7 +58,9 @@ typedef struct { bool use_gc_allocator; uint8_t half_width_px; - FIL file; + supervisor_vfs_file_t file; + // Which mount the file is open on, for locking around file reads. + supervisor_vfs_t *vfs; bool file_is_open; // Font metrics information loaded from file diff --git a/shared-module/memorymonitor/AllocationSize.c b/shared-module/memorymonitor/AllocationSize.c index f68401a252f..8c73f15db7e 100644 --- a/shared-module/memorymonitor/AllocationSize.c +++ b/shared-module/memorymonitor/AllocationSize.c @@ -60,6 +60,9 @@ void memorymonitor_allocationsizes_track_allocation(size_t block_count) { power_of_two++; block_count >>= 1; } + if (power_of_two >= ALLOCATION_SIZE_BUCKETS) { + power_of_two = ALLOCATION_SIZE_BUCKETS - 1; + } while (as != NULL) { as->buckets[power_of_two]++; as = as->next; diff --git a/shared-module/msgpack/__init__.c b/shared-module/msgpack/__init__.c index 7c3e664bb6c..f0099dd0ccb 100644 --- a/shared-module/msgpack/__init__.c +++ b/shared-module/msgpack/__init__.c @@ -9,6 +9,7 @@ #include "py/obj.h" #include "py/binary.h" +#include "py/cstack.h" #include "py/objarray.h" #include "py/objlist.h" #include "py/objstringio.h" @@ -254,6 +255,7 @@ static void pack_dict(msgpack_stream_t *s, size_t len) { } static void pack(mp_obj_t obj, msgpack_stream_t *s, mp_obj_t default_handler) { + mp_cstack_check(); if (mp_obj_is_small_int(obj)) { // int int32_t x = MP_OBJ_SMALL_INT_VALUE(obj); @@ -293,11 +295,11 @@ static void pack(mp_obj_t obj, msgpack_stream_t *s, mp_obj_t default_handler) { pack(next->value, s, default_handler); } } else if (mp_obj_is_float(obj)) { - union Float { mp_float_t f; + union Float { float f; uint32_t u; }; union Float data; - data.f = mp_obj_float_get(obj); + data.f = (float)mp_obj_float_get(obj); write1(s, 0xca); write4(s, data.u); } else if (obj == mp_const_none) { @@ -326,7 +328,14 @@ static void pack(mp_obj_t obj, msgpack_stream_t *s, mp_obj_t default_handler) { static mp_obj_t unpack(msgpack_stream_t *s, mp_obj_t ext_hook, bool use_list); +static void check_container_size(size_t count, size_t elem_size, qstr name) { + if (count > SIZE_MAX / elem_size) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("%q too long"), name); + } +} + static mp_obj_t unpack_array_elements(msgpack_stream_t *s, size_t size, mp_obj_t ext_hook, bool use_list) { + check_container_size(size, sizeof(mp_obj_t), MP_QSTR_array); if (use_list) { mp_obj_list_t *t = MP_OBJ_TO_PTR(mp_obj_new_list(size, NULL)); for (size_t i = 0; i < size; i++) { @@ -372,6 +381,7 @@ static mp_obj_t unpack_ext(msgpack_stream_t *s, size_t size, mp_obj_t ext_hook) } static mp_obj_t unpack(msgpack_stream_t *s, mp_obj_t ext_hook, bool use_list) { + mp_cstack_check(); uint8_t code = read1(s); if (((code & 0b10000000) == 0) || ((code & 0b11100000) == 0b11100000)) { // int @@ -431,12 +441,12 @@ static mp_obj_t unpack(msgpack_stream_t *s, mp_obj_t ext_hook, bool use_list) { return mp_obj_new_int_from_ll((int64_t)read8(s)); case 0xca: { // float union Float { - mp_float_t f; + float f; uint32_t u; }; union Float data; data.u = read4(s); - return mp_obj_new_float(data.f); + return mp_obj_new_float((mp_float_t)data.f); } case 0xcb: { // double union Double { @@ -462,6 +472,7 @@ static mp_obj_t unpack(msgpack_stream_t *s, mp_obj_t ext_hook, bool use_list) { case 0xdf: { // map 16 & 32 size_t len = read_size(s, code - 0xde + 1); + check_container_size(len, sizeof(mp_map_elem_t), MP_QSTR_map); mp_obj_dict_t *d = MP_OBJ_TO_PTR(mp_obj_new_dict(len)); for (size_t i = 0; i < len; i++) { mp_obj_t key = unpack(s, ext_hook, use_list); diff --git a/shared-module/os/__init__.c b/shared-module/os/__init__.c index 2090dcbcdc4..3a74fe4a7b0 100644 --- a/shared-module/os/__init__.c +++ b/shared-module/os/__init__.c @@ -145,10 +145,9 @@ const char *common_hal_os_path_abspath(const char *path) { } void common_hal_os_chdir(const char *path) { - MP_STATE_VM(cwd_path) = common_hal_os_path_abspath(path); + const char *new_cwd = common_hal_os_path_abspath(path); mp_obj_t path_out; - mp_vfs_mount_t *vfs = lookup_dir_path(MP_STATE_VM(cwd_path), &path_out); - MP_STATE_VM(vfs_cur) = vfs; + mp_vfs_mount_t *vfs = lookup_dir_path(new_cwd, &path_out); if (vfs == MP_VFS_ROOT) { // If we change to the root dir and a VFS is mounted at the root then // we must change that VFS's current dir to the root dir so that any @@ -163,6 +162,8 @@ void common_hal_os_chdir(const char *path) { } else { mp_vfs_proxy_call(vfs, MP_QSTR_chdir, 1, &path_out); } + MP_STATE_VM(cwd_path) = new_cwd; + MP_STATE_VM(vfs_cur) = vfs; } mp_obj_t common_hal_os_getcwd(void) { diff --git a/shared-module/picogame/Canvas.c b/shared-module/picogame/Canvas.c index 5021dbc95f9..2e694e20cab 100644 --- a/shared-module/picogame/Canvas.c +++ b/shared-module/picogame/Canvas.c @@ -9,7 +9,9 @@ #include "shared-module/picogame/Canvas.h" #include "shared-module/picogame/Bitmap.h" #include "shared-module/picogame/__init__.h" +#if CIRCUITPY_FONTIO #include "shared-module/fontio/BuiltinFont.h" +#endif #include "shared-bindings/displayio/Bitmap.h" // Thin wrappers over the shared int32 accumulator (dx1,dy1,dx2,dy2 are contiguous int32 at the @@ -50,7 +52,8 @@ static void mark(picogame_canvas_obj_t *cv, int lx1, int ly1, int lx2, int ly2) // 8 calls/iteration). Inlining bloated them (circle was ~1.4 KB); a real call keeps // them small. Shapes aren't the hot path (the sprite/tilemap blits don't use put). static __attribute__((noinline)) void put(picogame_canvas_obj_t *cv, int x, int y, uint16_t c) { - if (x >= 0 && y >= 0 && x < cv->w && y < cv->h) { + // Unsigned compares also reject negative coordinates. + if ((unsigned)x < (unsigned)cv->w && (unsigned)y < (unsigned)cv->h) { cv->data[y * cv->w + x] = c; } } @@ -60,10 +63,18 @@ static __attribute__((noinline)) void put(picogame_canvas_obj_t *cv, int x, int // so it stays safe on Cortex-M0+ (RP2040), which faults on an unaligned 32-bit access - a StripDraw // view's rows into the render strip can start on an odd pixel. This is the per-frame path for // view.clear / Sky / HUD-bar / Fade fills, so the word-fill is worth it. +// Not in SRAM: every caller is in flash, and the long-branch veneer made it slower. static void fill565(uint16_t *p, int n, uint16_t color) { if (n <= 0) { return; } + // Short spans (triangle rows, wall runs) skip the word path's setup. + if (n <= 4) { + do { + *p++ = color; + } while (--n); + return; + } if (color == 0) { memset(p, 0, (size_t)n * 2); return; @@ -77,10 +88,7 @@ static void fill565(uint16_t *p, int n, uint16_t color) { #pragma GCC diagnostic ignored "-Wcast-align" uint32_t *w32 = (uint32_t *)p; // now 4-byte aligned #pragma GCC diagnostic pop - int nw = n >> 1; - for (int i = 0; i < nw; i++) { - w32[i] = w; - } + picogame_fill_words(w32, n >> 1, w); if (n & 1) { // trailing odd pixel p[n - 1] = color; } @@ -177,7 +185,7 @@ typedef struct { int fmt, stride, shx, shy, mx, my, horizon, y_off; int32_t z, rx0, ry0, rsx, rsy, cam_x, cam_y; bool transp; - uint16_t key; + int32_t key; } mode7_ctx_t; static void mode7_rows(void *arg, int lo, int hi) { @@ -215,7 +223,7 @@ static void mode7_rows(void *arg, int lo, int hi) { for (int sx = 0; sx < w; sx++) { int tx = (fx >> c->shx) & c->mx, ty = (fy >> c->shy) & c->my; uint16_t val; - if (src_pixel_s(c->fmt, c->data, c->pal, c->transp, c->key, ty * c->stride + tx, &val)) { + if (src_pixel_s(c->fmt, c->data, c->pal, c->key, ty * c->stride + tx, &val)) { drow[sx] = val; } fx += stepx; @@ -245,8 +253,8 @@ void picogame_canvas_mode7(picogame_canvas_obj_t *cv, picogame_bitmap_obj_t *tex int fmt = tex->format; const uint8_t *data = tex->data; const uint16_t *pal = tex->palette; - bool transp = tex->has_transparent; - uint16_t key = tex->transparent; + bool transp = tex->has_transparent; // still gates the interp fast path above + int32_t key = picogame_key_of(tex); // sy is a row WITHIN this surface (a StripDraw view is a Canvas onto one strip); // the absolute screen row is sy + y_off, so the horizon test uses that. y_off = 0 // for a full-screen Canvas, = the strip's screen y for a StripDraw view (0-RAM floor). @@ -299,7 +307,8 @@ void picogame_canvas_line(picogame_canvas_obj_t *cv, int x0, int y0, int x1, int // Clamp a row span to the surface and word-fill it (the span-pass idiom shared by the filled // shapes; the per-pixel put() loops it replaced clipped and indexed every pixel). -static inline int64_t edge_slope(int32_t dx, int32_t dy) { +// Not inlined: each inlined copy carried both divides. +static __attribute__((noinline)) int64_t edge_slope(int32_t dx, int32_t dy) { if (dx >= -32768 && dx <= 32767) { return (int32_t)(dx << 16) / dy; } @@ -413,8 +422,15 @@ void picogame_canvas_fill_triangle(picogame_canvas_obj_t *cv, // top half: rows [Y0, Y1) walk edges A->C and A->B int ys = Y[0] < 0 ? 0 : Y[0]; int ye = (Y[1] - 1) < (h - 1) ? (Y[1] - 1) : (h - 1); - int64_t accAC = ((int64_t)X[0] << 16) + sAC * (ys - Y[0]); - int64_t acc2 = ((int64_t)X[0] << 16) + sAB * (ys - Y[0]); + // skip is 0 unless the triangle is clipped at the top, so the int64 multiplies + // usually do not run. + int skip = ys - Y[0]; + int64_t accAC = (int64_t)X[0] << 16; + int64_t acc2 = accAC; + if (skip) { + accAC += sAC * skip; + acc2 += sAB * skip; + } for (int y = ys; y <= ye; y++) { int xac = (int)(accAC >> 16); int xsh = (int)(acc2 >> 16); @@ -434,8 +450,12 @@ void picogame_canvas_fill_triangle(picogame_canvas_obj_t *cv, // bottom half: rows [Y1, Y2] walk edges A->C and B->C (a flat bottom degenerates to sBC=0) ys = Y[1] < 0 ? 0 : Y[1]; ye = Y[2] < (h - 1) ? Y[2] : (h - 1); - accAC = ((int64_t)X[0] << 16) + sAC * (ys - Y[0]); - acc2 = ((int64_t)X[1] << 16) + sBC * (ys - Y[1]); + accAC = ((int64_t)X[0] << 16) + sAC * (ys - Y[0]); // spans the whole top half: rarely 0 + acc2 = (int64_t)X[1] << 16; + skip = ys - Y[1]; + if (skip) { + acc2 += sBC * skip; + } for (int y = ys; y <= ye; y++) { int xac = (int)(accAC >> 16); int xsh = (int)(acc2 >> 16); @@ -558,6 +578,7 @@ void picogame_blit_canvas( // font's 1-bit atlas on the fly (no Python glyph cache, no per-call Bitmap/Sprite). Because the // StripDraw `view` is a Canvas pointing at the live strip buffer, view.text() draws immediate-mode // text into the frame with zero retained RAM - the same primitive serves retained Canvas screens. +#if CIRCUITPY_FONTIO void picogame_canvas_text(picogame_canvas_obj_t *cv, int x, int y, const char *text, uint16_t fg, uint16_t bg, bool has_bg, const void *font) { const fontio_builtinfont_t *f = font; @@ -573,6 +594,11 @@ void picogame_canvas_text(picogame_canvas_obj_t *cv, int x, int y, const char *t bool onebit = (sheet->bits_per_value == 1); // terminalio.FONT is 1-bpp; other fonts take the fallback const uint8_t *sdata = (const uint8_t *)sheet->data; int sstride_b = sheet->stride * 4; // atlas row stride in BYTES (stride counts uint32) + // Copy to locals: the uint16_t store may alias sheet->bitmask, so the fields + // would be reloaded for every pixel. + int sx_shift = sheet->x_shift; + size_t sx_mask = sheet->x_mask; + uint16_t sbitmask = sheet->bitmask; for (const uint8_t *p = (const uint8_t *)text; *p; p++) { uint8_t gi = fontio_builtinfont_get_glyph_index(f, *p); if (gi != 0xff) { // 0xff = no glyph -> blank advance @@ -588,7 +614,7 @@ void picogame_canvas_text(picogame_canvas_obj_t *cv, int x, int y, const char *t const uint8_t *srow = sdata + (size_t)sy * sstride_b; for (int gx = gx0; gx < gx1; gx++) { int sx = tx + gx; - if ((srow[sx >> sheet->x_shift] >> (sheet->x_mask - (sx & sheet->x_mask))) & sheet->bitmask) { + if ((srow[sx >> sx_shift] >> (sx_mask - ((size_t)sx & sx_mask))) & sbitmask) { drow[gx] = fg; } else if (has_bg) { drow[gx] = bg; @@ -609,6 +635,7 @@ void picogame_canvas_text(picogame_canvas_obj_t *cv, int x, int y, const char *t } mark(cv, x0, y, x, y + fh); } +#endif // Fill a screen-space triangle batch with per-triangle band reject - shared by the // Canvas.fill_triangles binding and the compositor's Triangles layer (one loop, one place). diff --git a/shared-module/picogame/Particles.c b/shared-module/picogame/Particles.c index 87e6917f479..401c382b0ee 100644 --- a/shared-module/picogame/Particles.c +++ b/shared-module/picogame/Particles.c @@ -34,9 +34,11 @@ static void swap_remove(picogame_particles_obj_t *ps, int i) { // Dim a wire-order RGB565 color to num/den of its brightness (per channel). static inline uint16_t scale_wire565(uint16_t wire, int num, int den) { uint16_t c = (uint16_t)((wire >> 8) | (wire << 8)); // wire -> native - int r = ((c >> 11) & 0x1F) * num / den; - int g = ((c >> 5) & 0x3F) * num / den; - int b = (c & 0x1F) * num / den; + // One Q8 reciprocal for all three channels (0 <= q <= 256). At most 1 LSB darker. + int q = (num << 8) / den; + int r = (((c >> 11) & 0x1F) * q) >> 8; + int g = (((c >> 5) & 0x3F) * q) >> 8; + int b = ((c & 0x1F) * q) >> 8; uint16_t out = (uint16_t)((r << 11) | (g << 5) | b); return (uint16_t)((out >> 8) | (out << 8)); // native -> wire } @@ -139,9 +141,18 @@ void picogame_blit_particles( int sz = ps->size; int rx2 = x0 + region_w; int ry2 = strip_top + strip_h; - for (int i = 0; i < ps->count; i++) { - int sx = (ps->px[i] >> 8) + ox; - int sy = (ps->py[i] >> 8) + oy; + // Reject on Y first: a strip spans the full width, so X rarely rejects. + const int32_t *pxs = ps->px, *pys = ps->py; + int ylo = strip_top - sz - oy; // py >> 8 <= ylo -> entirely above + int yhi = ry2 - oy; // py >> 8 >= yhi -> entirely below + int count = ps->count; + for (int i = 0; i < count; i++) { + int sy8 = pys[i] >> 8; + if (sy8 <= ylo || sy8 >= yhi) { + continue; + } + int sx = (pxs[i] >> 8) + ox; + int sy = sy8 + oy; int xs = picogame_imax(sx, x0); int ys = picogame_imax(sy, strip_top); int xe = picogame_imin(sx + sz, rx2); diff --git a/shared-module/picogame/__init__.c b/shared-module/picogame/__init__.c index ddd700959ed..8988d8b8a9c 100644 --- a/shared-module/picogame/__init__.c +++ b/shared-module/picogame/__init__.c @@ -115,6 +115,19 @@ static inline void picogame_fx_put(uint16_t *dst, uint16_t src, int x, int y, co } } +// Copy one row. memcpy when both pointers share word alignment; otherwise a +// halfword loop, because the bootrom memcpy is slow for misaligned copies. +static inline void picogame_row_copy(uint16_t *dst, const uint16_t *src, int n) { + if ((((uintptr_t)dst ^ (uintptr_t)src) & 2u) == 0) { + memcpy(dst, src, (size_t)n * 2u); + return; + } + #pragma GCC unroll 4 + for (int i = 0; i < n; i++) { + dst[i] = src[i]; + } +} + // Fetch one source pixel from HOISTED scalars: the caller lifts format/data/palette/transparency // out of the bitmap struct ONCE before its loop, so this does no per-pixel reload of bm fields (a // `*dst` uint16_t store would otherwise force GCC to reload bm's uint16_t members every pixel). `idx` @@ -165,8 +178,7 @@ void picogame_blit_bitmap( int t_fmt = bm->format; // hoist bm fields once (see src_pixel_s) const uint8_t *t_data = bm->data; const uint16_t *t_pal = bm->palette; - bool t_transp = bm->has_transparent; - uint16_t t_key = bm->transparent; + int32_t t_key = picogame_key_of(bm); for (int y = ys; y < ye; y++) { int ly = y - dy0; // -> source X (0..sw-1) int su = fx ? sw - 1 - ly : ly; // per-row: source column is constant across the row @@ -176,7 +188,7 @@ void picogame_blit_bitmap( int svstep = fy ? -1 : 1; for (int x = xs; x < xe; x++) { uint16_t val; - if (src_pixel_s(t_fmt, t_data, t_pal, t_transp, t_key, + if (src_pixel_s(t_fmt, t_data, t_pal, t_key, sv * stride0 + frame_col0 + su, &val)) { picogame_fx_put(dst, val, x, y, fxm); } @@ -198,6 +210,25 @@ void picogame_blit_bitmap( int frame_col = frame * sw; int stride = bm->stride; bool transp = bm->has_transparent; + int run = x_end - x_start; + int nrows = y_end - y_start; + + // Row invariants are computed once; GCC only unswitches the innermost loop. + int sy0 = y_start - dy0; + int systep = 1; + if (fy) { + sy0 = sh - 1 - sy0; + systep = -1; + } + int sx0 = x_start - dx0; + int xstep = 1; + if (fx) { + sx0 = sw - 1 - sx0; + xstep = -1; + } + int srow = sy0 * stride + frame_col; + int srow_step = systep * stride; + uint16_t *dstrow = buf + (y_start - oy) * bw + (x_start - ox); if (bm->format == PICOGAME_FMT_PAL8) { const uint8_t *data = bm->data; @@ -211,21 +242,15 @@ void picogame_blit_bitmap( // TO RESTORE FULL BOUNDS-SAFETY (at that cost) reinstate the clamp - add `unsigned pe = // bm->pal_entries;` here and `if (idx >= pe) { idx = 0; }` after each `idx = data[...]` in BOTH // loops below, and the matching guard in src_pixel() (search "blit contract"). - for (int y = y_start; y < y_end; y++) { - int sy = y - dy0; - if (fy) { - sy = sh - 1 - sy; - } - int srow = sy * stride + frame_col; - uint16_t *dst = buf + (y - oy) * bw + (x_start - ox); - int sx = x_start - dx0, xstep = 1; // hoist flip_x: walk sx +/-1, no per-pixel test - if (fx) { - sx = sw - 1 - sx; - xstep = -1; - } - if (fxm == NULL) { // plain copy (most sprites): no per-pixel fx branch/call + // + // Keep the transp flag here, not a -1 key: GCC clones these loops on the flag, + // and a sentinel made opaque PAL8 33% slower. + if (fxm == NULL) { // plain copy (most sprites): no per-pixel fx branch/call + for (int i = 0; i < nrows; i++) { + uint16_t *dst = dstrow; + int sx = sx0; #pragma GCC unroll 4 // hot path: unrolling the plain sprite blit is ~6% faster on M0+ (measured), +0.6KB - for (int x = x_start; x < x_end; x++) { + for (int x = 0; x < run; x++) { uint8_t idx = data[srow + sx]; if (!transp || idx != key) { *dst = pal[idx]; @@ -233,7 +258,14 @@ void picogame_blit_bitmap( dst++; sx += xstep; } - } else { + srow += srow_step; + dstrow += bw; + } + } else { + // The effect path keeps x/y as loop variables: DITHER uses screen coordinates. + for (int y = y_start; y < y_end; y++) { + uint16_t *dst = dstrow; + int sx = sx0; for (int x = x_start; x < x_end; x++) { uint8_t idx = data[srow + sx]; if (!transp || idx != key) { @@ -242,6 +274,8 @@ void picogame_blit_bitmap( dst++; sx += xstep; } + srow += srow_step; + dstrow += bw; } } } else { // PICOGAME_FMT_RGB565 @@ -252,26 +286,19 @@ void picogame_blit_bitmap( const uint16_t *data = (const uint16_t *)bm->data; #pragma GCC diagnostic pop uint16_t key = bm->transparent; - for (int y = y_start; y < y_end; y++) { - int sy = y - dy0; - if (fy) { - sy = sh - 1 - sy; + if (fxm == NULL && !transp && !fx) { + // Opaque, not x-flipped: one block copy per row. + for (int i = 0; i < nrows; i++) { + picogame_row_copy(dstrow, &data[srow + sx0], run); + srow += srow_step; + dstrow += bw; } - int srow = sy * stride + frame_col; - uint16_t *dst = buf + (y - oy) * bw + (x_start - ox); - int sx = x_start - dx0, xstep = 1; // hoist flip_x: walk sx +/-1, no per-pixel test - if (fx) { - sx = sw - 1 - sx; - xstep = -1; - } - if (fxm == NULL) { // plain copy (most sprites): no per-pixel fx branch/call - if (!transp && !fx) { // opaque + not x-flipped: the row is contiguous in - // both src and dst -> one memcpy (dst may be 2-byte aligned; memcpy handles that). - memcpy(dst, &data[srow + sx], (size_t)(x_end - x_start) * 2u); - continue; - } + } else if (fxm == NULL) { // plain copy (most sprites): no per-pixel fx branch/call + for (int i = 0; i < nrows; i++) { + uint16_t *dst = dstrow; + int sx = sx0; #pragma GCC unroll 4 // hot path: unrolling the plain sprite blit is ~6% faster on M0+ (measured), +0.6KB - for (int x = x_start; x < x_end; x++) { + for (int x = 0; x < run; x++) { uint16_t v = data[srow + sx]; if (!transp || v != key) { *dst = v; @@ -279,7 +306,14 @@ void picogame_blit_bitmap( dst++; sx += xstep; } - } else { + srow += srow_step; + dstrow += bw; + } + } else { + // The effect path keeps x/y as loop variables: DITHER uses screen coordinates. + for (int y = y_start; y < y_end; y++) { + uint16_t *dst = dstrow; + int sx = sx0; for (int x = x_start; x < x_end; x++) { uint16_t v = data[srow + sx]; if (!transp || v != key) { @@ -288,6 +322,8 @@ void picogame_blit_bitmap( dst++; sx += xstep; } + srow += srow_step; + dstrow += bw; } } } @@ -327,8 +363,8 @@ void picogame_blit_bitmap_scaled( int s_fmt = bm->format; // hoist bm fields once (see src_pixel_s) const uint8_t *s_data = bm->data; const uint16_t *s_pal = bm->palette; - bool s_transp = bm->has_transparent; - uint16_t s_key = bm->transparent; + bool s_transp = bm->has_transparent; // still selects the opaque fast paths below + int32_t s_key = picogame_key_of(bm); if (scale == 512 && !fx && !fy && fxm == NULL && !s_transp && s_fmt == PICOGAME_FMT_RGB565 && (((uintptr_t)s_data & 1) == 0)) { // 2x integer upscale fast path - the half-res-canvas genre's per-frame blit (a full-screen @@ -470,7 +506,7 @@ void picogame_blit_bitmap_scaled( sx = sw - 1 - sx; } uint16_t val; - if (src_pixel_s(s_fmt, s_data, s_pal, s_transp, s_key, srow + sx, &val)) { + if (src_pixel_s(s_fmt, s_data, s_pal, s_key, srow + sx, &val)) { picogame_fx_put(&drow[x - ox], val, x, y, fxm); } } @@ -491,11 +527,8 @@ static const int16_t pg_sin_q15_quad[91] = { 32269, 32364, 32448, 32523, 32587, 32642, 32687, 32722, 32747, 32762, 32767, }; -static int32_t pg_sin_q15(int deg) { - deg %= 360; - if (deg < 0) { - deg += 360; - } +// deg must already be in 0..360 (360 == 0). +static int32_t pg_sin_q15_reduced(int deg) { if (deg <= 90) { return pg_sin_q15_quad[deg]; } @@ -507,6 +540,13 @@ static int32_t pg_sin_q15(int deg) { } return -pg_sin_q15_quad[360 - deg]; } +static int32_t pg_sin_q15(int deg) { + deg %= 360; + if (deg < 0) { + deg += 360; + } + return pg_sin_q15_reduced(deg); +} static int32_t pg_cos_q15(int deg) { return pg_sin_q15(deg + 90); } @@ -515,10 +555,22 @@ static int32_t pg_cos_q15(int deg) { // DOUBLE-integrated over ~170 rows, which amplifies whole-degree quantization into visible pixels // (host-measured 9 px); one lerp per curvature eval brings the road within 1 px of the float original. static int32_t pg_sin_q15_lerp(int64_t deg_q16) { - int d0 = (int)(deg_q16 >> 16); + // Reduce once; d0 + 1 is then at most 360, which the table handles. + int d0 = (int)(deg_q16 >> 16) % 360; + if (d0 < 0) { + d0 += 360; + } int32_t frac = (int32_t)(deg_q16 & 0xFFFF); - int32_t a = pg_sin_q15(d0); - return a + (int32_t)(((int64_t)(pg_sin_q15(d0 + 1) - a) * frac) >> 16); + int32_t a = pg_sin_q15_reduced(d0); + // Adjacent entries differ by < 600 and frac < 2^16, so this fits in 32 bits. + return a + (((pg_sin_q15_reduced(d0 + 1) - a) * frac) >> 16); +} + +// (s * amp) >> 15 with 32-bit multiplies. Exact for |s| <= 2^15 and any int32 amp. +static inline int32_t pg_mulshr15(int32_t s, int32_t amp) { + uint32_t hi = (uint32_t)(amp >> 15) * (uint32_t)s; + int32_t lo = ((amp & 0x7FFF) * s) >> 15; + return (int32_t)(hi + (uint32_t)lo); } // One racing-road frame's curve pass: the bottom-up curvature accumulator + per-row integer edges @@ -539,8 +591,8 @@ void picogame_road_edges(int16_t *rl, int16_t *rr, const int32_t *hw_q16, int n, for (int i = n - 1; i >= 0; i--) { if (cnt == 0) { int32_t d = dist + (drow - i) * wstep; - ck = (int32_t)(((int64_t)pg_sin_q15_lerp(((int64_t)d * f1) >> 4) * a1k) >> 15) - + (int32_t)(((int64_t)pg_sin_q15_lerp(((int64_t)d * f2) >> 4) * a2k) >> 15); + ck = pg_mulshr15(pg_sin_q15_lerp(((int64_t)d * f1) >> 4), a1k) + + pg_mulshr15(pg_sin_q15_lerp(((int64_t)d * f2) >> 4), a2k); cnt = cstep; } cnt--; @@ -648,8 +700,7 @@ void picogame_blit_bitmap_affine( int a_fmt = bm->format; // hoist bm fields once (see src_pixel_s) const uint8_t *a_data = bm->data; const uint16_t *a_pal = bm->palette; - bool a_transp = bm->has_transparent; - uint16_t a_key = bm->transparent; + int32_t a_key = picogame_key_of(bm); int x_start = picogame_imax(minx, ox), y_start = picogame_imax(miny, oy); int x_end = picogame_imin(maxx + 1, ox + bw), y_end = picogame_imin(maxy + 1, oy + bh); if (x_start >= x_end || y_start >= y_end) { @@ -685,9 +736,10 @@ void picogame_blit_bitmap_affine( int su = uacc >> 16, sv = vacc >> 16; // already the flipped source coords uacc += uxc; vacc += vxc; - if (su >= 0 && su < sw && sv >= 0 && sv < sh) { + // Unsigned compares also reject negative coordinates. + if ((unsigned)su < (unsigned)sw && (unsigned)sv < (unsigned)sh) { uint16_t val; - if (src_pixel_s(a_fmt, a_data, a_pal, a_transp, a_key, + if (src_pixel_s(a_fmt, a_data, a_pal, a_key, sv * stride + frame_col + su, &val)) { picogame_fx_put(&drow[x - ox], val, x, y, fxm); } @@ -815,9 +867,7 @@ mp_obj_t picogame_blit_strip_layers( uint32_t *w32 = (uint32_t *)(buf + i); // now 4-byte aligned #pragma GCC diagnostic pop int nw = (npix - i) >> 1; - for (int k = 0; k < nw; k++) { - w32[k] = w; - } + picogame_fill_words(w32, nw, w); i += nw << 1; if (i < npix) { // odd trailing pixel buf[i] = background; @@ -911,6 +961,14 @@ mp_obj_t picogame_blit_strip_layers( if (!(spr->flags & PICOGAME_SPR_VISIBLE)) { continue; } + // CIRCUITPY-CHANGE: skip strips the sprite does not touch before entering the + // blitter, which picks the effect and may bake a palette first. + int ax1, ay1, ax2, ay2; + picogame_sprite_aabb(spr, &ax1, &ay1, &ax2, &ay2); + if (ay1 + ioy >= strip_top + strip_h || ay2 + ioy <= strip_top || + ax1 + iox >= x0 + region_w || ax2 + iox <= x0) { + continue; + } blit_sprite(buf, region_w, strip_h, x0, strip_top, spr, iox, ioy); } } @@ -976,6 +1034,12 @@ bool picogame_strip_begin( return true; } +#if CIRCUITPY_PICOGAME_RGB444 +// The display currently switched to RGB444, or NULL. The strip path packs for it and +// picogame_reset() restores it to RGB565 at the end of the program. +static picogame_output_t *rgb444_display = NULL; +#endif + // --- output transport seam (busdisplay backend): the ONLY per-strip display ops the generic // picogame_render_region orchestrator below touches, so a non-CircuitPython port (MicroPython // framebuf/SPI) swaps just strip_begin + these two + set_invert/set_pixel_format. See __init__.h. @@ -993,6 +1057,14 @@ void picogame_render_region( int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t background, int ox, int oy) { + #if CIRCUITPY_PICOGAME_RGB444 + // RGB444 packs 2 px into 3 bytes, so widen the region to even x bounds. + bool rgb444 = (display == rgb444_display); + if (rgb444) { + x0 &= ~1; + x1 = (x1 + 1) & ~1; + } + #endif int region_w, strip_h; int cx0 = x0, cy0 = y0, cx1 = x1, cy1 = y1; // strip_begin clamps these to the panel in place if (!picogame_strip_begin(display, &cx0, &cy0, &cx1, &cy1, buffer_pixels, ®ion_w, &strip_h)) { @@ -1001,7 +1073,13 @@ void picogame_render_region( for (int sy = cy0; sy < cy1; sy += strip_h) { int sh = picogame_imin(strip_h, cy1 - sy); mp_obj_t exc = picogame_blit_strip_layers(buffer, region_w, sy, sh, cx0, items, kinds, n, background, ox, oy); - picogame_out_strip_send(display, (uint8_t *)buffer, region_w * sh * 2); + size_t nbytes = (size_t)region_w * sh * 2; + #if CIRCUITPY_PICOGAME_RGB444 + if (rgb444) { + nbytes = picogame_pack_rgb444(buffer, (size_t)region_w * sh); // in place, 3/4 the bytes + } + #endif + picogame_out_strip_send(display, (uint8_t *)buffer, nbytes); if (exc != MP_OBJ_NULL) { // a StripDraw callback raised a BaseException: close the picogame_out_strip_end(display); // bus, then re-raise (Ctrl-C / reload) nlr_raise(MP_OBJ_TO_PTR(exc)); @@ -1029,6 +1107,7 @@ void picogame_set_invert(picogame_output_t *display, bool on) { // Set the panel pixel format (COLMOD 0x3A): rgb444 -> 12-bit RGB444 (0x53), else 16-bit RGB565 // (0x55). Asserting it on every Display construct also recovers from a previous program that left // the panel in the other format (survives soft reset). + void picogame_set_pixel_format(picogame_output_t *display, bool rgb444) { uint8_t cmd = 0x3A; uint8_t param = rgb444 ? 0x53 : 0x55; @@ -1038,6 +1117,20 @@ void picogame_set_pixel_format(picogame_output_t *display, bool rgb444) { display->bus.send(display->bus.bus, DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, &cmd, 1); display->bus.send(display->bus.bus, DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, ¶m, 1); displayio_display_bus_end_transaction(&display->bus); + rgb444_display = rgb444 ? display : NULL; +} + +// Called at the end of a program, before reset_displays() takes the bus away. +void picogame_reset(void) { + if (rgb444_display == NULL) { + return; + } + // A released display has a None bus. + if (mp_obj_get_type(rgb444_display->bus.bus) == &mp_type_NoneType) { + rgb444_display = NULL; + return; + } + picogame_set_pixel_format(rgb444_display, false); // clears rgb444_display } // Pack a strip of `npix` (must be even) WIRE-order RGB565 pixels IN-PLACE to ST7789 12-bit RGB444 diff --git a/shared-module/picogame/__init__.h b/shared-module/picogame/__init__.h index a4377292219..12868ae274a 100644 --- a/shared-module/picogame/__init__.h +++ b/shared-module/picogame/__init__.h @@ -27,13 +27,33 @@ #endif #endif +// Fill nw 32-bit words with w, four per iteration. w32 must be 4-byte aligned. +static inline void picogame_fill_words(uint32_t *w32, int nw, uint32_t w) { + for (int b = nw >> 2; b > 0; b--) { + w32[0] = w; + w32[1] = w; + w32[2] = w; + w32[3] = w; + w32 += 4; + } + if (nw & 2) { + w32[0] = w; + w32[1] = w; + w32 += 2; + } + if (nw & 1) { + w32[0] = w; + } +} + // Sample one texel as wire RGB565; false = transparent (skip). Shared by the sprite/canvas // blit paths so they inline one copy (see the blit contract: PAL8 indices must be < palette len). +// key is the transparent value, or -1 for an opaque bitmap (never matches). static inline bool src_pixel_s(int format, const uint8_t *data, const uint16_t *pal, - bool transp, uint16_t key, int idx, uint16_t *out) { + int32_t key, int idx, uint16_t *out) { if (format == PICOGAME_FMT_PAL8) { uint8_t i = data[idx]; - if (transp && i == (uint8_t)key) { + if ((int32_t)i == key) { return false; } *out = pal[i]; // indices must be < palette length (see blit contract) @@ -44,13 +64,18 @@ static inline bool src_pixel_s(int format, const uint8_t *data, const uint16_t * #pragma GCC diagnostic ignored "-Wcast-align" uint16_t v = ((const uint16_t *)data)[idx]; #pragma GCC diagnostic pop - if (transp && v == key) { + if ((int32_t)v == key) { return false; } *out = v; return true; } +// The transparent key of bm, or -1 when it is opaque. +static inline int32_t picogame_key_of(const picogame_bitmap_obj_t *bm) { + return bm->has_transparent ? (int32_t)bm->transparent : -1; +} + // Scene layer kinds (tags stored alongside items so blit/dirty can dispatch // without cross-referencing shared-bindings type objects). @@ -184,7 +209,8 @@ void picogame_blit_bitmap_affine( // // Strip-path contract a backend provides: // picogame_strip_begin - open a window for [x0,y0,x1,y1); return strip geometry -// picogame_out_strip_send - push one composited strip (region_w*sh px, wire RGB565) +// picogame_out_strip_send - push one composited strip (region_w*sh px, wire RGB565; packed to +// RGB444 first when that panel is in 12-bit mode) // picogame_out_strip_end - close the transaction // picogame_set_invert - panel hardware colour inversion (a free full-screen flash) // picogame_set_pixel_format - panel COLMOD (RGB565/RGB444), when CIRCUITPY_PICOGAME_RGB444 @@ -248,6 +274,10 @@ bool picogame_fb_take_invert_dirty(void); // Set panel pixel format (COLMOD): rgb444 -> 12-bit RGB444, else 16-bit RGB565. void picogame_set_pixel_format(picogame_output_t *display, bool rgb444); +// Put a panel left in RGB444 back to RGB565 at the end of a program, so the console stays +// readable. Call before reset_displays(). +void picogame_reset(void); + // Pack `npix` (even) wire-order RGB565 pixels in `buf` IN-PLACE to 12-bit RGB444; returns bytes. size_t picogame_pack_rgb444(uint16_t *buf, size_t npix); #endif diff --git a/shared-module/rainbowio/__init__.c b/shared-module/rainbowio/__init__.c index 8fdc8867666..20fd83a4fc4 100644 --- a/shared-module/rainbowio/__init__.c +++ b/shared-module/rainbowio/__init__.c @@ -7,7 +7,10 @@ #include "shared-bindings/rainbowio/__init__.h" int32_t colorwheel(mp_float_t pos) { - pos = pos - ((uint32_t)(pos / 256) * 256); + pos = pos - (mp_float_t)((int32_t)(pos / 256) * 256); + if (pos < 0) { + pos += 256; + } int shift1, shift2; if (pos < 85) { shift1 = 8; diff --git a/shared-module/random/__init__.c b/shared-module/random/__init__.c index 26d11313962..a45da46574a 100644 --- a/shared-module/random/__init__.c +++ b/shared-module/random/__init__.c @@ -20,9 +20,11 @@ static uint32_t yasmarang_pad = 0xeda4baba, yasmarang_n = 69, yasmarang_d = 233; static uint8_t yasmarang_dat = 0; +static bool yasmarang_seeded = false; static uint32_t yasmarang(void) { - if (yasmarang_pad == 0xeda4baba) { + if (!yasmarang_seeded) { + yasmarang_seeded = true; if (!common_hal_os_urandom((uint8_t *)&yasmarang_pad, sizeof(uint32_t))) { yasmarang_pad = common_hal_time_monotonic_ms() & 0xffffffff; } @@ -57,6 +59,7 @@ void shared_modules_random_seed(mp_uint_t seed) { yasmarang_n = 69; yasmarang_d = 233; yasmarang_dat = 0; + yasmarang_seeded = true; } mp_uint_t shared_modules_random_getrandbits(uint8_t n) { @@ -79,6 +82,16 @@ mp_int_t shared_modules_random_randrange(mp_int_t start, mp_int_t stop, mp_int_t return start + step * yasmarang_randbelow(n); } +mp_int_t shared_modules_random_randint(mp_int_t a, mp_int_t b) { + // Compute the span unsigned so that b - a + 1 cannot overflow for any a <= b. + mp_uint_t n = (mp_uint_t)b - (mp_uint_t)a + 1; + if (n == 0) { + // a and b cover the full mp_int_t range: any value is in range. + return a + (mp_int_t)yasmarang(); + } + return a + yasmarang_randbelow(n); +} + // returns a number in the range [0..1) using Yasmarang to fill in the fraction bits static mp_float_t yasmarang_float(void) { #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE diff --git a/shared-module/rgbmatrix/RGBMatrix.c b/shared-module/rgbmatrix/RGBMatrix.c index db14c6a6b0d..3cc2a65a376 100644 --- a/shared-module/rgbmatrix/RGBMatrix.c +++ b/shared-module/rgbmatrix/RGBMatrix.c @@ -15,6 +15,7 @@ #include "common-hal/rgbmatrix/RGBMatrix.h" #include "shared-module/rgbmatrix/allocator.h" #include "shared-bindings/rgbmatrix/RGBMatrix.h" +#include "shared-bindings/digitalio/DigitalInOut.h" #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/microcontroller/__init__.h" #include "shared-bindings/util.h" @@ -22,18 +23,123 @@ extern Protomatter_core *_PM_protoPtr; -static void common_hal_rgbmatrix_rgbmatrix_construct1(rgbmatrix_rgbmatrix_obj_t *self, mp_obj_t framebuffer); +static void common_hal_rgbmatrix_rgbmatrix_construct1(rgbmatrix_rgbmatrix_obj_t *self, mp_obj_t framebuffer, ProtomatterRowAddressMode row_address_mode); -void common_hal_rgbmatrix_rgbmatrix_construct(rgbmatrix_rgbmatrix_obj_t *self, int width, int bit_depth, uint8_t rgb_count, uint8_t *rgb_pins, uint8_t addr_count, uint8_t *addr_pins, uint8_t clock_pin, uint8_t latch_pin, uint8_t oe_pin, bool doublebuffer, mp_obj_t framebuffer, int8_t tile, bool serpentine, void *timer) { +static void preflight_pins_or_throw(const mcu_pin_obj_t *clock_pin_obj, const mcu_pin_obj_t **rgb_pin_objs, uint8_t rgb_pin_count, bool allow_inefficient) { + if (rgb_pin_count <= 0 || rgb_pin_count % 6 != 0 || rgb_pin_count > 30) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("The length of rgb_pins must be 6, 12, 18, 24, or 30")); + } + +// Most ports have a strict requirement for how the rgbmatrix pins are laid +// out; these two micros don't. Special-case it here. + #if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2) + uint8_t clock_pin = common_hal_mcu_pin_number(clock_pin_obj); + uint32_t port = clock_pin / 32; + uint32_t bit_mask = 1 << (clock_pin % 32); + + for (uint8_t i = 0; i < rgb_pin_count; i++) { + uint8_t pin_number = common_hal_mcu_pin_number(rgb_pin_objs[i]); + uint32_t pin_port = pin_number / 32; + + if (pin_port != port) { + mp_raise_ValueError_varg( + MP_ERROR_TEXT("rgb_pins[%d] is not on the same port as clock"), i); + } + + uint32_t pin_mask = 1 << (pin_number % 32); + if (pin_mask & bit_mask) { + mp_raise_ValueError_varg( + MP_ERROR_TEXT("rgb_pins[%d] duplicates another pin assignment"), i); + } + + bit_mask |= pin_mask; + } + + if (allow_inefficient) { + return; + } + + uint8_t byte_mask = 0; + if (bit_mask & 0x000000FF) { + byte_mask |= 0b0001; + } + if (bit_mask & 0x0000FF00) { + byte_mask |= 0b0010; + } + if (bit_mask & 0x00FF0000) { + byte_mask |= 0b0100; + } + if (bit_mask & 0xFF000000) { + byte_mask |= 0b1000; + } + + uint8_t bytes_per_element = 0xff; + uint8_t ideal_bytes_per_element = (rgb_pin_count + 7) / 8; + + switch (byte_mask) { + case 0b0001: + case 0b0010: + case 0b0100: + case 0b1000: + bytes_per_element = 1; + break; + + case 0b0011: + case 0b1100: + bytes_per_element = 2; + break; + + default: + bytes_per_element = 4; + break; + } + + if (bytes_per_element != ideal_bytes_per_element) { + mp_raise_ValueError_varg( + MP_ERROR_TEXT("Pinout uses %d bytes per element, which consumes more than the ideal %d bytes. If this cannot be avoided, pass allow_inefficient=True to the constructor"), + bytes_per_element, ideal_bytes_per_element); + } + #endif +} + +// Claim each pin by wrapping it in a DigitalInOut object owned by the matrix. +// The DigitalInOut lives on the port heap (not the VM heap) because the matrix +// itself lives in the static display bus storage and survives VM resets. +static void store_pin_digitalinout(rgbmatrix_rgbmatrix_obj_t *self, const mcu_pin_obj_t *pin) { + digitalio_digitalinout_obj_t *digitalinout = mp_obj_port_malloc(digitalio_digitalinout_obj_t, &digitalio_digitalinout_type); + if (digitalinout == NULL) { + m_malloc_fail(sizeof(digitalio_digitalinout_obj_t)); + } + common_hal_digitalio_digitalinout_construct(digitalinout, pin); + #if CIRCUITPY_BULK_RESET + common_hal_digitalio_digitalinout_never_reset(digitalinout); + #endif + self->pin_digitalinouts[self->pin_digitalinout_count++] = digitalinout; +} + +void common_hal_rgbmatrix_rgbmatrix_construct(rgbmatrix_rgbmatrix_obj_t *self, int width, int bit_depth, uint8_t rgb_count, const mcu_pin_obj_t **rgb_pins, uint8_t addr_count, const mcu_pin_obj_t **addr_pins, const mcu_pin_obj_t *clock_pin, const mcu_pin_obj_t *latch_pin, const mcu_pin_obj_t *oe_pin, bool doublebuffer, mp_obj_t framebuffer, int8_t tile, bool serpentine, void *timer, uint8_t row_addr_count, ProtomatterRowAddressMode row_address_mode) { self->width = width; self->bit_depth = bit_depth; self->rgb_count = rgb_count; - memcpy(self->rgb_pins, rgb_pins, rgb_count); + preflight_pins_or_throw(clock_pin, rgb_pins, rgb_count, true); + // Claim and configure each pin by wrapping it in a DigitalInOut owned by the + // matrix. This runs before Protomatter configures the pins so its setup wins. + for (uint8_t i = 0; i < rgb_count; i++) { + store_pin_digitalinout(self, rgb_pins[i]); + self->rgb_pins[i] = common_hal_mcu_pin_number(rgb_pins[i]); + } self->addr_count = addr_count; - memcpy(self->addr_pins, addr_pins, addr_count); - self->clock_pin = clock_pin; - self->oe_pin = oe_pin; - self->latch_pin = latch_pin; + self->row_addr_count = row_addr_count; + for (uint8_t i = 0; i < addr_count; i++) { + store_pin_digitalinout(self, addr_pins[i]); + self->addr_pins[i] = common_hal_mcu_pin_number(addr_pins[i]); + } + store_pin_digitalinout(self, clock_pin); + self->clock_pin = common_hal_mcu_pin_number(clock_pin); + store_pin_digitalinout(self, oe_pin); + self->oe_pin = common_hal_mcu_pin_number(oe_pin); + store_pin_digitalinout(self, latch_pin); + self->latch_pin = common_hal_mcu_pin_number(latch_pin); self->doublebuffer = doublebuffer; self->tile = tile; self->serpentine = serpentine; @@ -46,10 +152,10 @@ void common_hal_rgbmatrix_rgbmatrix_construct(rgbmatrix_rgbmatrix_obj_t *self, i self->width = width; self->bufsize = 2 * width * common_hal_rgbmatrix_rgbmatrix_get_height(self); - common_hal_rgbmatrix_rgbmatrix_construct1(self, framebuffer); + common_hal_rgbmatrix_rgbmatrix_construct1(self, framebuffer, row_address_mode); } -static void common_hal_rgbmatrix_rgbmatrix_construct1(rgbmatrix_rgbmatrix_obj_t *self, mp_obj_t framebuffer) { +static void common_hal_rgbmatrix_rgbmatrix_construct1(rgbmatrix_rgbmatrix_obj_t *self, mp_obj_t framebuffer, ProtomatterRowAddressMode row_address_mode) { if (framebuffer != mp_const_none) { mp_get_buffer_raise(self->framebuffer, &self->bufinfo, MP_BUFFER_READ); if (mp_get_buffer(self->framebuffer, &self->bufinfo, MP_BUFFER_RW)) { @@ -70,13 +176,13 @@ static void common_hal_rgbmatrix_rgbmatrix_construct1(rgbmatrix_rgbmatrix_obj_t self->framebuffer = framebuffer; memset(&self->protomatter, 0, sizeof(self->protomatter)); - ProtomatterStatus stat = _PM_init(&self->protomatter, + ProtomatterStatus stat = _PM_init_with_row_address_mode(&self->protomatter, self->width, self->bit_depth, self->rgb_count / 6, self->rgb_pins, - self->addr_count, self->addr_pins, + self->row_addr_count, self->addr_pins, self->clock_pin, self->latch_pin, self->oe_pin, self->doublebuffer, self->serpentine ? -self->tile : self->tile, - self->timer); + self->timer, row_address_mode); if (stat == PROTOMATTER_OK) { _PM_protoPtr = &self->protomatter; @@ -114,17 +220,13 @@ static void common_hal_rgbmatrix_rgbmatrix_construct1(rgbmatrix_rgbmatrix_obj_t self->paused = 0; } -static void free_pin(uint8_t *pin) { - if (*pin != COMMON_HAL_MCU_NO_PIN) { - common_hal_mcu_pin_reset_number(*pin); - } - *pin = COMMON_HAL_MCU_NO_PIN; -} - -static void free_pin_seq(uint8_t *seq, int count) { - for (int i = 0; i < count; i++) { - free_pin(&seq[i]); +static void deinit_pin_digitalinouts(rgbmatrix_rgbmatrix_obj_t *self) { + for (uint8_t i = 0; i < self->pin_digitalinout_count; i++) { + common_hal_digitalio_digitalinout_deinit(self->pin_digitalinouts[i]); + port_free(self->pin_digitalinouts[i]); + self->pin_digitalinouts[i] = NULL; } + self->pin_digitalinout_count = 0; } extern int pm_row_count; @@ -160,11 +262,7 @@ void common_hal_rgbmatrix_rgbmatrix_deinit(rgbmatrix_rgbmatrix_obj_t *self) { self->timer = 0; } - free_pin_seq(self->rgb_pins, self->rgb_count); - free_pin_seq(self->addr_pins, self->addr_count); - free_pin(&self->clock_pin); - free_pin(&self->latch_pin); - free_pin(&self->oe_pin); + deinit_pin_digitalinouts(self); self->base.type = &mp_type_NoneType; } @@ -221,6 +319,6 @@ int common_hal_rgbmatrix_rgbmatrix_get_width(rgbmatrix_rgbmatrix_obj_t *self) { } int common_hal_rgbmatrix_rgbmatrix_get_height(rgbmatrix_rgbmatrix_obj_t *self) { - int computed_height = (self->rgb_count / 3) * (1 << (self->addr_count)) * self->tile; + int computed_height = (self->rgb_count / 3) * (1 << self->row_addr_count) * self->tile; return computed_height; } diff --git a/shared-module/rgbmatrix/RGBMatrix.h b/shared-module/rgbmatrix/RGBMatrix.h index 197bbb264af..6104293b5e9 100644 --- a/shared-module/rgbmatrix/RGBMatrix.h +++ b/shared-module/rgbmatrix/RGBMatrix.h @@ -8,6 +8,7 @@ #include "py/obj.h" #include "lib/protomatter/src/core.h" +#include "shared-bindings/digitalio/DigitalInOut.h" extern const mp_obj_type_t rgbmatrix_RGBMatrix_type; typedef struct { @@ -21,11 +22,15 @@ typedef struct { uint8_t rgb_pins[30]; uint8_t addr_pins[10]; uint8_t clock_pin, latch_pin, oe_pin; - uint8_t rgb_count, addr_count; + uint8_t rgb_count, addr_count, row_addr_count; uint8_t bit_depth; bool core_is_initialized; bool paused; bool doublebuffer; bool serpentine; int8_t tile; + // DigitalInOut objects that own each of the pins. They claim the pins and + // release them when they are deinitialized. + digitalio_digitalinout_obj_t *pin_digitalinouts[30 + 10 + 3]; + uint8_t pin_digitalinout_count; } rgbmatrix_rgbmatrix_obj_t; diff --git a/shared-module/sdcardio/__init__.c b/shared-module/sdcardio/__init__.c index d53f7a3fabd..75ebe5cdfef 100644 --- a/shared-module/sdcardio/__init__.c +++ b/shared-module/sdcardio/__init__.c @@ -40,8 +40,10 @@ void sdcardio_init(void) { sd_card_detect_pin.base.type = &digitalio_digitalinout_type; common_hal_digitalio_digitalinout_construct(&sd_card_detect_pin, DEFAULT_SD_CARD_DETECT); common_hal_digitalio_digitalinout_switch_to_input(&sd_card_detect_pin, PULL_UP); + #if CIRCUITPY_BULK_RESET common_hal_digitalio_digitalinout_never_reset(&sd_card_detect_pin); #endif + #endif } void automount_sd_card(void) { @@ -97,8 +99,10 @@ void automount_sd_card(void) { spi_obj = &busio_spi_obj; spi_obj->base.type = &busio_spi_type; common_hal_busio_spi_construct(spi_obj, DEFAULT_SD_SCK, DEFAULT_SD_MOSI, DEFAULT_SD_MISO, false); + #if CIRCUITPY_BULK_RESET common_hal_busio_spi_never_reset(spi_obj); #endif + #endif sdcard.base.type = &sdcardio_SDCard_type; mp_obj_t cs_obj = MP_OBJ_FROM_PTR(DEFAULT_SD_CS); mp_rom_error_text_t error = sdcardio_sdcard_construct(&sdcard, spi_obj, cs_obj, 25000000, true); @@ -112,7 +116,9 @@ void automount_sd_card(void) { return; } if (mp_obj_is_type(cs_obj, &mcu_pin_type)) { + #if CIRCUITPY_BULK_RESET common_hal_digitalio_digitalinout_never_reset(MP_OBJ_TO_PTR(sdcard.cs)); + #endif } fs_user_mount_t *vfs = &_sdcard_usermount; vfs->base.type = &mp_fat_vfs_type; @@ -134,8 +140,8 @@ void automount_sd_card(void) { return; } - filesystem_set_concurrent_write_protection(vfs, true); - filesystem_set_writable_by_usb(vfs, false); + filesystem_set_concurrent_write_protection((supervisor_vfs_t *)vfs, true); + filesystem_set_writable_by_usb((supervisor_vfs_t *)vfs, false); mp_vfs_mount_t *sdcard_vfs = &_sdcard_vfs; sdcard_vfs->str = "/sd"; diff --git a/shared-module/sharpdisplay/SharpMemoryFramebuffer.c b/shared-module/sharpdisplay/SharpMemoryFramebuffer.c index 8b9629b002f..90c3fd2e85c 100644 --- a/shared-module/sharpdisplay/SharpMemoryFramebuffer.c +++ b/shared-module/sharpdisplay/SharpMemoryFramebuffer.c @@ -105,7 +105,7 @@ void common_hal_sharpdisplay_framebuffer_deinit(sharpdisplay_framebuffer_obj_t * common_hal_busio_spi_deinit(self->bus); } - common_hal_reset_pin(self->chip_select.pin); + common_hal_digitalio_digitalinout_deinit(&self->chip_select); port_free(self->bufinfo.buf); @@ -122,10 +122,14 @@ void common_hal_sharpdisplay_framebuffer_construct( bool jdi_display) { common_hal_digitalio_digitalinout_construct(&self->chip_select, chip_select); common_hal_digitalio_digitalinout_switch_to_output(&self->chip_select, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_never_reset_pin(chip_select); + #endif self->bus = spi; + #if CIRCUITPY_BULK_RESET common_hal_busio_spi_never_reset(self->bus); + #endif self->width = width; self->height = height; diff --git a/shared-module/storage/__init__.c b/shared-module/storage/__init__.c index 1522136332a..4a5561c3164 100644 --- a/shared-module/storage/__init__.c +++ b/shared-module/storage/__init__.c @@ -145,8 +145,11 @@ void common_hal_storage_mount(mp_obj_t vfs_obj, const char *mount_path, bool rea fs_user_mount_t *vfs_fat = MP_OBJ_TO_PTR(vfs_obj); // Filesystem is read-only to USB if writable by CircuitPython, and vice versa. - filesystem_set_writable_by_usb(vfs_fat, readonly); - filesystem_set_concurrent_write_protection(vfs_fat, true); + // The flag helpers take a supervisor_vfs_t; a mounted VfsFat is always a + // fs_user_mount_t, whose base + blockdev prefix matches both vfs + // kinds. + filesystem_set_writable_by_usb((supervisor_vfs_t *)vfs_fat, readonly); + filesystem_set_concurrent_write_protection((supervisor_vfs_t *)vfs_fat, true); // Insert the vfs into the mount table by pushing it onto the front of the // mount table. @@ -197,29 +200,37 @@ mp_obj_t common_hal_storage_getmount(const char *mount_path) { return storage_object_from_path(mount_path); } -void common_hal_storage_remount(const char *mount_path, bool readonly, bool disable_concurrent_write_protection) { - const char *path_under_mount; - const char *abs_mount_path = common_hal_os_path_abspath(mount_path); - fs_user_mount_t *fs_usermount = filesystem_for_path(abs_mount_path, &path_under_mount); - if (path_under_mount[0] != 0 && strcmp(abs_mount_path, "/") != 0) { - mp_raise_OSError(MP_EINVAL); - } - +static void remount_vfs(supervisor_vfs_t *fs_mount, bool readonly, bool disable_concurrent_write_protection) { #if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_MSC - if (!blockdev_lock(fs_usermount)) { + if (!blockdev_lock(fs_mount)) { mp_raise_RuntimeError(MP_ERROR_TEXT("Cannot remount path when visible via USB.")); } #endif - filesystem_set_writable_by_usb(fs_usermount, readonly); - filesystem_set_concurrent_write_protection(fs_usermount, !disable_concurrent_write_protection); - blockdev_unlock(fs_usermount); + filesystem_set_writable_by_usb(fs_mount, readonly); + filesystem_set_concurrent_write_protection(fs_mount, !disable_concurrent_write_protection); + blockdev_unlock(fs_mount); #if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_MSC - usb_msc_remount(fs_usermount); + usb_msc_remount(&fs_mount->fat); #endif } +void common_hal_storage_remount(const char *mount_path, bool readonly, bool disable_concurrent_write_protection) { + const char *path_under_mount; + const char *abs_mount_path = common_hal_os_path_abspath(mount_path); + supervisor_vfs_t *fs_mount = filesystem_for_path(abs_mount_path, &path_under_mount); + if (path_under_mount[0] != 0 && strcmp(abs_mount_path, "/") != 0) { + mp_raise_OSError(MP_EINVAL); + } + if (fs_mount == NULL) { + // Nothing is mounted there (or there is no filesystem at all). + mp_raise_OSError(MP_EINVAL); + } + + remount_vfs(fs_mount, readonly, disable_concurrent_write_protection); +} + void common_hal_storage_erase_filesystem(bool extended) { #if CIRCUITPY_USB_DEVICE usb_disconnect(); diff --git a/shared-module/struct/__init__.c b/shared-module/struct/__init__.c index 4b6b2b589ef..ce71d06b754 100644 --- a/shared-module/struct/__init__.c +++ b/shared-module/struct/__init__.c @@ -47,7 +47,7 @@ static mp_uint_t get_fmt_num(const char **p) { while (unichar_isdigit(*++num)) { len++; } - mp_uint_t val = (mp_uint_t)MP_OBJ_SMALL_INT_VALUE(mp_parse_num_integer(*p, len, 10, NULL)); + mp_uint_t val = (mp_uint_t)mp_obj_get_int(mp_parse_num_integer(*p, len, 10, NULL)); *p = num; return val; } diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index 1cccd440fad..7c54c36d47f 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -19,6 +19,7 @@ static mp_obj_t parse_note(synthio_miditrack_obj_t *self) { size_t len = self->track.len; if (self->pos + 1 >= len) { record_midi_stream_error(self); + return MP_OBJ_NEW_SMALL_INT(0); } uint8_t note = buffer[(self->pos)++]; if (note > 127 || buffer[(self->pos)++] > 127) { @@ -51,6 +52,9 @@ static void decode_until_pause(synthio_miditrack_obj_t *self) { uint8_t *buffer = self->track.buf; size_t len = self->track.len; do { + if (self->pos >= len) { + break; + } switch (buffer[self->pos++] >> 4) { case 8: { // Note Off mp_obj_t note = parse_note(self); diff --git a/shared-module/synthio/__init__.c b/shared-module/synthio/__init__.c index 368bae24140..6201ad96347 100644 --- a/shared-module/synthio/__init__.c +++ b/shared-module/synthio/__init__.c @@ -224,14 +224,13 @@ static bool synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT; uint32_t accum = synth->accum[chan]; - if (dds_rate > lim / 2) { + if (dds_rate > (lim - offset) / 2) { // beyond nyquist, can't play note return false; } - // can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided - if (accum > lim) { - accum = accum % lim + offset; + if (accum >= lim) { + accum = accum < offset ? offset : offset + (accum - offset) % (lim - offset); } // first, fill with waveform @@ -247,20 +246,19 @@ static bool synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou synth->accum[chan] = accum; if (ring_dds_rate) { - if (ring_dds_rate > lim / 2) { + accum = synth->ring_accum[chan]; + offset = ring_waveform_start << SYNTHIO_FREQUENCY_SHIFT; + lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT; + + if (ring_dds_rate > (lim - offset) / 2) { // beyond nyquist, can't play ring (but did synth main sound so // return true) return true; } - // now modulate by ring and accumulate - accum = synth->ring_accum[chan]; - offset = ring_waveform_start << SYNTHIO_FREQUENCY_SHIFT; - lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT; - // can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided - if (accum > lim) { - accum = accum % lim + offset; + if (accum >= lim) { + accum = accum < offset ? offset : offset + (accum - offset) % (lim - offset); } for (uint16_t i = 0; i < dur; i++) { @@ -270,8 +268,8 @@ static bool synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou accum = accum - lim + offset; } int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT; - int16_t wi = (ring_waveform[idx] * out_buffer32[i]) / 32768; // consider for synthio_sat16 but had a weird artificat - out_buffer32[i] = wi; + int32_t wi = (ring_waveform[idx] * out_buffer32[i]) / 32768; + out_buffer32[i] = wi > 32767 ? 32767 : wi; } synth->ring_accum[chan] = accum; } diff --git a/shared-module/touchio/TouchIn.c b/shared-module/touchio/TouchIn.c index 268055d14bf..c81d8bcf26d 100644 --- a/shared-module/touchio/TouchIn.c +++ b/shared-module/touchio/TouchIn.c @@ -54,7 +54,6 @@ static uint16_t get_raw_reading(touchio_touchin_obj_t *self) { } void common_hal_touchio_touchin_construct(touchio_touchin_obj_t *self, const mcu_pin_obj_t *pin, const digitalio_pull_t pull) { - common_hal_mcu_pin_claim(pin); self->digitalinout = mp_obj_malloc(digitalio_digitalinout_obj_t, &digitalio_digitalinout_type); common_hal_digitalio_digitalinout_construct(self->digitalinout, pin); diff --git a/shared-module/usb_cdc/__init__.c b/shared-module/usb_cdc/__init__.c index 644c4ccae79..dd15905e116 100644 --- a/shared-module/usb_cdc/__init__.c +++ b/shared-module/usb_cdc/__init__.c @@ -204,6 +204,32 @@ size_t usb_cdc_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *desc descriptor_counts->num_out_endpoints++; descriptor_counts->current_endpoint++; + #if CIRCUITPY_FULL_BUILD + const char *control_interface_name = + console ? console_cdc_comm_interface_name : data_cdc_comm_interface_name; + const char *data_interface_name = + console ? console_cdc_data_interface_name : data_cdc_data_interface_name; + + char *custom_control_interface_name = + console ? custom_usb_cdc_console_control_interface_name : custom_usb_cdc_data_control_interface_name; + char *custom_data_interface_name = + console ? custom_usb_cdc_console_data_interface_name : custom_usb_cdc_data_data_interface_name; + + if (custom_control_interface_name != NULL) { + control_interface_name = custom_control_interface_name; + } + if (custom_data_interface_name != NULL) { + data_interface_name = custom_data_interface_name; + } + + usb_add_interface_string(*current_interface_string, control_interface_name); + descriptor_buf[CDC_COMM_INTERFACE_STRING_INDEX] = *current_interface_string; + (*current_interface_string)++; + + usb_add_interface_string(*current_interface_string, data_interface_name); + descriptor_buf[CDC_DATA_INTERFACE_STRING_INDEX] = *current_interface_string; + (*current_interface_string)++; + #else usb_add_interface_string(*current_interface_string, console ? console_cdc_comm_interface_name : data_cdc_comm_interface_name); descriptor_buf[CDC_COMM_INTERFACE_STRING_INDEX] = *current_interface_string; @@ -213,6 +239,7 @@ size_t usb_cdc_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *desc console ? console_cdc_data_interface_name : data_cdc_data_interface_name); descriptor_buf[CDC_DATA_INTERFACE_STRING_INDEX] = *current_interface_string; (*current_interface_string)++; + #endif return sizeof(usb_cdc_descriptor_template); } diff --git a/shared-module/warnings/__init__.c b/shared-module/warnings/__init__.c index ad2f14b1c5b..9961c6caa7f 100644 --- a/shared-module/warnings/__init__.c +++ b/shared-module/warnings/__init__.c @@ -21,9 +21,8 @@ void common_hal_warnings_warn(const char *message, const mp_obj_type_t *category return; } if (action == WARNINGS_ERROR) { - mp_raise_msg_str(category, message); - // Doesn't get here - return; + nlr_raise(mp_call_function_1(MP_OBJ_FROM_PTR(category), + mp_obj_new_str(message, strlen(message)))); } mp_printf(MICROPY_ERROR_PRINTER, "%q: %s\n", category->name, message); } diff --git a/shared/libc/string0.c b/shared/libc/string0.c index 5a9e0ff853a..614581734cb 100644 --- a/shared/libc/string0.c +++ b/shared/libc/string0.c @@ -28,6 +28,7 @@ #include // CIRCUITPY-CHANGE: additional includes +#include // for the atoi() prototype #include #include "py/mpconfig.h" @@ -54,14 +55,14 @@ void *memcpy(void *dst, const void *src, size_t n) { if (n & 2) { // copy half-word - *(uint16_t*)d = *(const uint16_t*)s; - d = (uint32_t*)((uint16_t*)d + 1); - s = (const uint32_t*)((const uint16_t*)s + 1); + *(uint16_t *)d = *(const uint16_t *)s; + d = (uint32_t *)((uint16_t *)d + 1); + s = (const uint32_t *)((const uint16_t *)s + 1); } if (n & 1) { // copy byte - *((uint8_t*)d) = *((const uint8_t*)s); + *((uint8_t *)d) = *((const uint8_t *)s); } } else #endif @@ -88,10 +89,10 @@ void *__memcpy_chk(void *dest, const void *src, size_t len, size_t slen) { } void *memmove(void *dest, const void *src, size_t n) { - if (src < dest && (uint8_t*)dest < (const uint8_t*)src + n) { + if (src < dest && (uint8_t *)dest < (const uint8_t *)src + n) { // need to copy backwards - uint8_t *d = (uint8_t*)dest + n - 1; - const uint8_t *s = (const uint8_t*)src + n - 1; + uint8_t *d = (uint8_t *)dest + n - 1; + const uint8_t *s = (const uint8_t *)src + n - 1; for (; n > 0; n--) { *d-- = *s--; } @@ -112,11 +113,11 @@ void *memset(void *s, int c, size_t n) { *s32++ = 0; } if (n & 2) { - *((uint16_t*)s32) = 0; - s32 = (uint32_t*)((uint16_t*)s32 + 1); + *((uint16_t *)s32) = 0; + s32 = (uint32_t *)((uint16_t *)s32 + 1); } if (n & 1) { - *((uint8_t*)s32) = 0; + *((uint8_t *)s32) = 0; } } else #endif @@ -138,8 +139,11 @@ int memcmp(const void *s1, const void *s2, size_t n) { while (n--) { char c1 = *s1_8++; char c2 = *s2_8++; - if (c1 < c2) return -1; - else if (c1 > c2) return 1; + if (c1 < c2) { + return -1; + } else if (c1 > c2) { + return 1; + } } return 0; } @@ -149,8 +153,9 @@ void *memchr(const void *s, int c, size_t n) { const unsigned char *p = s; do { - if (*p++ == c) - return ((void *)(p - 1)); + if (*p++ == c) { + return (void *)(p - 1); + } } while (--n != 0); } return 0; @@ -168,12 +173,19 @@ int strcmp(const char *s1, const char *s2) { while (*s1 && *s2) { char c1 = *s1++; // XXX UTF8 get char, next char char c2 = *s2++; // XXX UTF8 get char, next char - if (c1 < c2) return -1; - else if (c1 > c2) return 1; + if (c1 < c2) { + return -1; + } else if (c1 > c2) { + return 1; + } + } + if (*s2) { + return -1; + } else if (*s1) { + return 1; + } else { + return 0; } - if (*s2) return -1; - else if (*s1) return 1; - else return 0; } int strncmp(const char *s1, const char *s2, size_t n) { @@ -181,13 +193,21 @@ int strncmp(const char *s1, const char *s2, size_t n) { char c1 = *s1++; // XXX UTF8 get char, next char char c2 = *s2++; // XXX UTF8 get char, next char n--; - if (c1 < c2) return -1; - else if (c1 > c2) return 1; + if (c1 < c2) { + return -1; + } else if (c1 > c2) { + return 1; + } + } + if (n == 0) { + return 0; + } else if (*s2) { + return -1; + } else if (*s1) { + return 1; + } else { + return 0; } - if (n == 0) return 0; - else if (*s2) return -1; - else if (*s1) return 1; - else return 0; } char *strcpy(char *dest, const char *src) { @@ -202,21 +222,21 @@ char *strcpy(char *dest, const char *src) { // Public Domain implementation of strncpy from: // http://en.wikibooks.org/wiki/C_Programming/Strings#The_strncpy_function char *strncpy(char *s1, const char *s2, size_t n) { - char *dst = s1; - const char *src = s2; - /* Copy bytes, one at a time. */ - while (n > 0) { - n--; - if ((*dst++ = *src++) == '\0') { - /* If we get here, we found a null character at the end - of s2, so use memset to put null bytes at the end of - s1. */ - memset(dst, '\0', n); - break; - } - } - return s1; - } + char *dst = s1; + const char *src = s2; + /* Copy bytes, one at a time. */ + while (n > 0) { + n--; + if ((*dst++ = *src++) == '\0') { + /* If we get here, we found a null character at the end + of s2, so use memset to put null bytes at the end of + s1. */ + memset(dst, '\0', n); + break; + } + } + return s1; +} // needed because gcc optimises strcpy + strcat to this char *stpcpy(char *dest, const char *src) { @@ -241,29 +261,31 @@ char *strcat(char *dest, const char *src) { // Public Domain implementation of strchr from: // http://en.wikibooks.org/wiki/C_Programming/Strings#The_strchr_function -char *strchr(const char *s, int c) -{ +char *strchr(const char *s, int c) { /* Scan s for the character. When this loop is finished, s will either point to the end of the string or the character we were looking for. */ - while (*s != '\0' && *s != (char)c) + while (*s != '\0' && *s != (char)c) { s++; - return ((*s == c) ? (char *) s : 0); + } + return (*s == c) ? (char *)s : 0; } // Public Domain implementation of strstr from: // http://en.wikibooks.org/wiki/C_Programming/Strings#The_strstr_function -char *strstr(const char *haystack, const char *needle) -{ +char *strstr(const char *haystack, const char *needle) { size_t needlelen; /* Check for the null needle case. */ - if (*needle == '\0') - return (char *) haystack; + if (*needle == '\0') { + return (char *)haystack; + } needlelen = strlen(needle); - for (; (haystack = strchr(haystack, *needle)) != 0; haystack++) - if (strncmp(haystack, needle, needlelen) == 0) - return (char *) haystack; + for (; (haystack = strchr(haystack, *needle)) != 0; haystack++) { + if (strncmp(haystack, needle, needlelen) == 0) { + return (char *)haystack; + } + } return 0; } @@ -282,3 +304,13 @@ size_t strcspn(const char *s, const char *reject) { } return s - ss; } + +// Decimal-only, non-negative integers; no leading whitespace handling. +// Marked weak so a libc-provided atoi() takes precedence if available. +__attribute__((weak)) int atoi(const char *num) { + int value = 0; + while (*num >= '0' && *num <= '9') { + value = value * 10 + (*num++ - '0'); + } + return value; +} diff --git a/shared/memzip/lexermemzip.c b/shared/memzip/lexermemzip.c index aef64ffa0a7..a31fc7f1cdc 100644 --- a/shared/memzip/lexermemzip.c +++ b/shared/memzip/lexermemzip.c @@ -5,8 +5,7 @@ #include "py/mperrno.h" #include "memzip.h" -mp_lexer_t *mp_lexer_new_from_file(qstr filename) -{ +mp_lexer_t *mp_lexer_new_from_file(qstr filename) { void *data; size_t len; diff --git a/shared/memzip/make-memzip.py b/shared/memzip/make-memzip.py index e406c55a43c..c060745e6cf 100755 --- a/shared/memzip/make-memzip.py +++ b/shared/memzip/make-memzip.py @@ -9,9 +9,10 @@ import argparse import os +import pathlib +import shutil import subprocess import sys -import types def create_zip(zip_filename, zip_dir): @@ -26,7 +27,7 @@ def create_zip(zip_filename, zip_dir): def create_c_from_file(c_filename, zip_filename): with open(zip_filename, "rb") as zip_file: - with open(c_filename, "wb") as c_file: + with open(c_filename, "wt") as c_file: print("#include ", file=c_file) print("", file=c_file) print("const uint8_t memzip_data[] = {", file=c_file) @@ -36,10 +37,7 @@ def create_c_from_file(c_filename, zip_filename): break print(" ", end="", file=c_file) for byte in buf: - if isinstance(byte, types.StringType): - print(" 0x{:02x},".format(ord(byte)), end="", file=c_file) - else: - print(" 0x{:02x},".format(byte), end="", file=c_file) + print(" 0x{:02x},".format(byte), end="", file=c_file) print("", file=c_file) print("};", file=c_file) @@ -67,10 +65,20 @@ def main(): parser.add_argument(dest="source_dir", default="memzip_files") args = parser.parse_args(sys.argv[1:]) + output_zip = pathlib.Path(args.zip_filename) + if output_zip.suffix != ".zip": + args.zip_filename = output_zip.with_suffix(".zip") + output_c = pathlib.Path(args.c_filename) + if output_c.suffix != ".c": + args.c_filename = output_c.with_suffix(".c") + print("args.zip_filename =", args.zip_filename) print("args.c_filename =", args.c_filename) print("args.source_dir =", args.source_dir) + if not shutil.which("zip"): + raise FileNotFoundError("zip archiver not available") + create_zip(args.zip_filename, args.source_dir) create_c_from_file(args.c_filename, args.zip_filename) diff --git a/shared/memzip/memzip.c b/shared/memzip/memzip.c index 3fbea8e1e91..32fd27ffeb4 100644 --- a/shared/memzip/memzip.c +++ b/shared/memzip/memzip.c @@ -65,8 +65,7 @@ bool memzip_is_dir(const char *filename) { } -MEMZIP_RESULT memzip_locate(const char *filename, void **data, size_t *len) -{ +MEMZIP_RESULT memzip_locate(const char *filename, void **data, size_t *len) { const MEMZIP_FILE_HDR *file_hdr = memzip_find_file_header(filename); if (file_hdr == NULL) { return MZ_NO_FILE; diff --git a/shared/memzip/memzip.h b/shared/memzip/memzip.h index 667e2df7e13..fe27d9584a3 100644 --- a/shared/memzip/memzip.h +++ b/shared/memzip/memzip.h @@ -3,17 +3,17 @@ #define MEMZIP_FILE_HEADER_SIGNATURE 0x04034b50 typedef struct { - uint32_t signature; - uint16_t version; - uint16_t flags; - uint16_t compression_method; - uint16_t last_mod_time; - uint16_t last_mod_date; - uint32_t crc32; - uint32_t compressed_size; - uint32_t uncompressed_size; - uint16_t filename_len; - uint16_t extra_len; + uint32_t signature; + uint16_t version; + uint16_t flags; + uint16_t compression_method; + uint16_t last_mod_time; + uint16_t last_mod_date; + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; + uint16_t filename_len; + uint16_t extra_len; /* char filename[filename_len] */ /* uint8_t extra[extra_len] */ @@ -23,22 +23,22 @@ typedef struct #define MEMZIP_CENTRAL_DIRECTORY_SIGNATURE 0x02014b50 typedef struct { - uint32_t signature; - uint16_t version_made_by; - uint16_t version_read_with; - uint16_t flags; - uint16_t compression_method; - uint16_t last_mod_time; - uint16_t last_mod_date; - uint32_t crc32; - uint32_t compressed_size; - uint32_t uncompressed_size; - uint16_t filename_len; - uint16_t extra_len; - uint16_t disk_num; - uint16_t internal_file_attributes; - uint32_t external_file_attributes; - uint32_t file_header_offset; + uint32_t signature; + uint16_t version_made_by; + uint16_t version_read_with; + uint16_t flags; + uint16_t compression_method; + uint16_t last_mod_time; + uint16_t last_mod_date; + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; + uint16_t filename_len; + uint16_t extra_len; + uint16_t disk_num; + uint16_t internal_file_attributes; + uint32_t external_file_attributes; + uint32_t file_header_offset; /* char filename[filename_len] */ /* uint8_t extra[extra_len] */ @@ -48,14 +48,14 @@ typedef struct #define MEMZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE 0x06054b50 typedef struct { - uint32_t signature; - uint16_t disk_num; - uint16_t central_directory_disk; - uint16_t num_central_directories_this_disk; - uint16_t total_central_directories; - uint32_t central_directory_size; - uint32_t central_directory_offset; - uint16_t comment_len; + uint32_t signature; + uint16_t disk_num; + uint16_t central_directory_disk; + uint16_t num_central_directories_this_disk; + uint16_t total_central_directories; + uint32_t central_directory_size; + uint32_t central_directory_offset; + uint16_t comment_len; /* char comment[comment_len] */ @@ -71,10 +71,10 @@ typedef enum { } MEMZIP_RESULT; typedef struct { - uint32_t file_size; - uint16_t last_mod_date; - uint16_t last_mod_time; - uint8_t is_dir; + uint32_t file_size; + uint16_t last_mod_date; + uint16_t last_mod_time; + uint8_t is_dir; } MEMZIP_FILE_INFO; diff --git a/shared/netutils/dhcpserver.c b/shared/netutils/dhcpserver.c index 6d9cdb97d1f..038b794e864 100644 --- a/shared/netutils/dhcpserver.c +++ b/shared/netutils/dhcpserver.c @@ -69,11 +69,9 @@ #define PORT_DHCP_SERVER (67) #define PORT_DHCP_CLIENT (68) -#define DEFAULT_DNS MAKE_IP4(192, 168, 4, 1) #define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds #define MAC_LEN (6) -#define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) typedef struct { uint8_t op; // message opcode @@ -287,8 +285,10 @@ static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, opt_write_n(&opt, DHCP_OPT_SERVER_ID, 4, &ip_2_ip4(&d->ip)->addr); opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, &ip_2_ip4(&d->nm)->addr); - opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &ip_2_ip4(&d->ip)->addr); // aka gateway; can have multiple addresses - opt_write_u32(&opt, DHCP_OPT_DNS, DEFAULT_DNS); // can have multiple addresses + if (d->send_router) { + opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &ip_2_ip4(&d->ip)->addr); // aka gateway; can have multiple addresses + } + opt_write_n(&opt, DHCP_OPT_DNS, 4, &ip_2_ip4(&d->ip)->addr); opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S); *opt++ = DHCP_OPT_END; struct netif *netif = ip_current_input_netif(); @@ -302,6 +302,7 @@ void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) { ip_addr_copy(d->ip, *ip); ip_addr_copy(d->nm, *nm); memset(d->lease, 0, sizeof(d->lease)); + d->send_router = true; if (dhcp_socket_new_dgram(&d->udp, d, dhcp_server_process) != 0) { return; } diff --git a/shared/netutils/dhcpserver.h b/shared/netutils/dhcpserver.h index 2349d2ea427..24224d6aadd 100644 --- a/shared/netutils/dhcpserver.h +++ b/shared/netutils/dhcpserver.h @@ -41,6 +41,7 @@ typedef struct _dhcp_server_t { ip_addr_t nm; dhcp_server_lease_t lease[DHCPS_MAX_IP]; struct udp_pcb *udp; + bool send_router; // advertise server IP as default gateway } dhcp_server_t; void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm); diff --git a/shared/runtime/buffer_helper.c b/shared/runtime/buffer_helper.c index a1f7f7bb7f6..29e486af2c9 100644 --- a/shared/runtime/buffer_helper.c +++ b/shared/runtime/buffer_helper.c @@ -14,6 +14,9 @@ void normalize_buffer_bounds(int32_t *start, int32_t end, size_t *length) { } if (*start < 0) { *start += *length; + if (*start < 0) { + *start = 0; + } } if (end < *start) { *length = 0; diff --git a/shared/runtime/gchelper.h b/shared/runtime/gchelper.h index 1e85e06f46e..9617bc7f9fe 100644 --- a/shared/runtime/gchelper.h +++ b/shared/runtime/gchelper.h @@ -43,6 +43,10 @@ typedef uintptr_t gc_helper_regs_t[10]; typedef uintptr_t gc_helper_regs_t[11]; // x19-x29 #elif defined(__riscv) && (__riscv_xlen <= 64) typedef uintptr_t gc_helper_regs_t[12]; // S0-S11 +#elif defined(__loongarch__) && defined(__loongarch64) +typedef uintptr_t gc_helper_regs_t[10]; // S0-S9 +#elif defined(__powerpc__) && defined(__powerpc64__) +typedef uintptr_t gc_helper_regs_t[18]; // r14-r31 #endif #endif diff --git a/shared/runtime/gchelper_generic.c b/shared/runtime/gchelper_generic.c index 230a2444005..dcca800d512 100644 --- a/shared/runtime/gchelper_generic.c +++ b/shared/runtime/gchelper_generic.c @@ -190,6 +190,73 @@ static void gc_helper_get_regs(gc_helper_regs_t arr) { arr[11] = s11; } +#elif defined(__loongarch__) && defined(__loongarch64) + +// Fallback implementation for LOONG64, prefer gchelper_loong64.s. +static void gc_helper_get_regs(gc_helper_regs_t arr) { + register uintptr_t s0 asm ("r23"); + register uintptr_t s1 asm ("r24"); + register uintptr_t s2 asm ("r25"); + register uintptr_t s3 asm ("r26"); + register uintptr_t s4 asm ("r27"); + register uintptr_t s5 asm ("r28"); + register uintptr_t s6 asm ("r29"); + register uintptr_t s7 asm ("r30"); + register uintptr_t s8 asm ("r31"); + register uintptr_t s9 asm ("r22"); + arr[0] = s0; + arr[1] = s1; + arr[2] = s2; + arr[3] = s3; + arr[4] = s4; + arr[5] = s5; + arr[6] = s6; + arr[7] = s7; + arr[8] = s8; + arr[9] = s9; +} + +#elif defined(__powerpc__) && defined(__powerpc64__) + +static void gc_helper_get_regs(gc_helper_regs_t arr) { + register uintptr_t r14 __asm("r14"); + register uintptr_t r15 __asm("r15"); + register uintptr_t r16 __asm("r16"); + register uintptr_t r17 __asm("r17"); + register uintptr_t r18 __asm("r18"); + register uintptr_t r19 __asm("r19"); + register uintptr_t r20 __asm("r20"); + register uintptr_t r21 __asm("r21"); + register uintptr_t r22 __asm("r22"); + register uintptr_t r23 __asm("r23"); + register uintptr_t r24 __asm("r24"); + register uintptr_t r25 __asm("r25"); + register uintptr_t r26 __asm("r26"); + register uintptr_t r27 __asm("r27"); + register uintptr_t r28 __asm("r28"); + register uintptr_t r29 __asm("r29"); + register uintptr_t r30 __asm("r30"); + register uintptr_t r31 __asm("r31"); + arr[0] = r14; + arr[1] = r15; + arr[2] = r16; + arr[3] = r17; + arr[4] = r18; + arr[5] = r19; + arr[6] = r20; + arr[7] = r21; + arr[8] = r22; + arr[9] = r23; + arr[10] = r24; + arr[11] = r25; + arr[12] = r26; + arr[13] = r27; + arr[14] = r28; + arr[15] = r29; + arr[16] = r30; + arr[17] = r31; +} + #else #error "Architecture not supported for gc_helper_get_regs. Set MICROPY_GCREGS_SETJMP to use the fallback implementation." diff --git a/shared/runtime/gchelper_loong64.s b/shared/runtime/gchelper_loong64.s new file mode 100644 index 00000000000..5180e1e24df --- /dev/null +++ b/shared/runtime/gchelper_loong64.s @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2026 Alessandro Gatti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + .global gc_helper_get_regs_and_sp + .type gc_helper_get_regs_and_sp, @function + +gc_helper_get_regs_and_sp: + + /* Store registers into the given array. */ + + st.d $r23, $r4, 0 /* Save S0. */ + st.d $r24, $r4, 8 /* Save S1. */ + st.d $r25, $r4, 16 /* Save S2. */ + st.d $r26, $r4, 24 /* Save S3. */ + st.d $r27, $r4, 32 /* Save S4. */ + st.d $r28, $r4, 40 /* Save S5. */ + st.d $r29, $r4, 48 /* Save S6. */ + st.d $r30, $r4, 56 /* Save S7. */ + st.d $r31, $r4, 64 /* Save S8. */ + st.d $r22, $r4, 72 /* Save S9. */ + + /* Return the stack pointer. */ + + add.d $r4, $r0, $r3 + jirl $r0, $r1, 0 + + .size gc_helper_get_regs_and_sp, .-gc_helper_get_regs_and_sp diff --git a/shared/runtime/pyexec.c b/shared/runtime/pyexec.c index 2b57830abfc..0c10ebf0e94 100644 --- a/shared/runtime/pyexec.c +++ b/shared/runtime/pyexec.c @@ -133,7 +133,7 @@ static int parse_compile_execute(const void *source, mp_parse_input_kind_t input mp_parse_tree_t parse_tree = mp_parse(lex, input_kind); #if defined(MICROPY_UNIX_COVERAGE) // allow to print the parse tree in the coverage build - if (mp_verbose_flag >= 3) { + if (MP_STATE_VM(mp_verbose_flag) >= 3) { printf("----------------\n"); mp_parse_node_print(&mp_plat_print, parse_tree.root, 0); printf("----------------\n"); @@ -545,6 +545,8 @@ static int pyexec_friendly_repl_process_char(int c) { vstr_reset(MP_STATE_VM(repl_line)); repl.paste_mode = true; return 0; + } else if (vstr_len(MP_STATE_VM(repl_line)) == 0) { + goto input_restart; } if (ret < 0) { diff --git a/supervisor/fatfs.h b/supervisor/fatfs.h index e212664210d..0dc1f3b6876 100644 --- a/supervisor/fatfs.h +++ b/supervisor/fatfs.h @@ -6,6 +6,14 @@ #pragma once +#include + #include "lib/oofatfs/ff.h" void override_fattime(DWORD time); + +// Current time in nanoseconds past 1970/1/1, read from the same RTC source +// that get_fattime() uses. When there is no RTC, this is the same fixed +// fallback timestamp get_fattime() falls back to, so every filesystem kind +// stamps files with the same "current time". +uint64_t get_fattime_ns(void); diff --git a/supervisor/filesystem.h b/supervisor/filesystem.h index 30cd83d4438..3b2f84aacdf 100644 --- a/supervisor/filesystem.h +++ b/supervisor/filesystem.h @@ -9,9 +9,173 @@ #include #include "extmod/vfs_fat.h" +#if CIRCUITPY_FILESYSTEM_LITTLEFS +#include "extmod/vfs_lfs.h" +#endif + +// A supervisor-managed filesystem object, which is either a FAT fs_user_mount_t +// or, on littlefs builds, an mp_obj_vfs_lfs2_t. Both begin with the same +// mp_obj_base_t + mp_vfs_blockdev_t prefix, so a bare fs_user_mount_t (e.g. an +// SD card mount) can also be cast to supervisor_vfs_t and inspected through +// .common. The two kinds are told apart by base.type: &mp_fat_vfs_type vs +// &mp_type_vfs_lfs2. +typedef union _supervisor_vfs_t { + struct { + mp_obj_base_t base; + mp_vfs_blockdev_t blockdev; + } common; + fs_user_mount_t fat; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + mp_obj_vfs_lfs2_t lfs2; + #endif +} supervisor_vfs_t; extern volatile bool filesystem_flush_requested; +// Supervisor-level filesystem API. These functions work on both FAT and +// littlefs mounts so that supervisor code (workflows, settings) doesn't have to +// care which one is active. These APIs do not raise Python exceptions or use VM +// heap allocated memory so they are safe use when the Python VM isn't active. + +// Open flags for supervisor_vfs_open_file(). +#define SUPERVISOR_FS_OPEN_READ 0x01 +#define SUPERVISOR_FS_OPEN_WRITE 0x02 +// Create the file if it doesn't exist (with WRITE). An existing file keeps its +// contents. +#define SUPERVISOR_FS_OPEN_CREATE 0x04 +// Empty an existing file on open (with WRITE|CREATE). +#define SUPERVISOR_FS_OPEN_TRUNCATE 0x08 + +// Error codes shared by the supervisor filesystem API. +#if CIRCUITPY_FILESYSTEM_LITTLEFS + +// When littlefs is enabled, FatFS and littlefs results are mapped to these +// shared codes so call sites can be shared between FAT and littlefs. +typedef enum { + SUPERVISOR_FS_OK = 0, + SUPERVISOR_FS_NO_FILE, // File or directory doesn't exist. + SUPERVISOR_FS_NO_PATH, // A path component is missing (or bad). + SUPERVISOR_FS_EXIST, // File or directory already exists. + SUPERVISOR_FS_WRITE_PROTECTED, // Filesystem or media is not writable. + SUPERVISOR_FS_NO_SPACE, // No space left on the filesystem. + SUPERVISOR_FS_IO, // Any other error. +} supervisor_fs_err_t; + +// littlefs mtime attribute id, matching extmod/vfs_lfs.c: +// 64-bit little endian, nanoseconds since 1970/1/1. +#ifndef LFS_ATTR_MTIME +#define LFS_ATTR_MTIME (1) +#endif + +#else + +// On FAT-only builds the errors are FatFS FRESULT values so +// that supervisor_vfs_* calls compile down to plain FatFS calls without any +// error translation, keeping non-littlefs builds the same size as before the +// supervisor filesystem API was added. The littlefs build's enum above maps +// onto the same FRESULT subset that the workflows use. +typedef FRESULT supervisor_fs_err_t; +#define SUPERVISOR_FS_OK FR_OK +#define SUPERVISOR_FS_NO_FILE FR_NO_FILE +#define SUPERVISOR_FS_NO_PATH FR_NO_PATH +#define SUPERVISOR_FS_EXIST FR_EXIST +#define SUPERVISOR_FS_WRITE_PROTECTED FR_WRITE_PROTECTED +#define SUPERVISOR_FS_NO_SPACE FR_DENIED +#define SUPERVISOR_FS_IO FR_INT_ERR + +#endif + +// Handle for a file opened with supervisor_vfs_open_file(). +typedef struct _supervisor_vfs_file_t { + bool open; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + bool lfs; + // Which mount the file is open on. Unused on FAT, where the FIL carries + // its own filesystem. + supervisor_vfs_t *vfs; + #endif + // FAT: timestamp to stamp the file with on close (0 = current time). + DWORD fattime; + union { + FIL fat; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + struct { + lfs2_file_t lfs2; + struct lfs2_file_config cfg; + struct lfs2_attr attr; + uint8_t mtime[8]; + uint8_t buffer[FILESYSTEM_BLOCK_SIZE]; + } lfs; + #endif + } file; +} supervisor_vfs_file_t; + +// Directory handle for supervisor_vfs_opendir(). +typedef struct _supervisor_vfs_dir_t { + bool open; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + bool lfs; + // Directory path used to build per-entry paths for littlefs, which keeps + // modification times in per-path attributes. Unused on FAT. + #define SUPERVISOR_VFS_DIR_PATH_MAX (256) + char path[SUPERVISOR_VFS_DIR_PATH_MAX]; + // Which mount the directory is open on. Unused on FAT. + supervisor_vfs_t *vfs; + #endif + union { + FF_DIR fat; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + lfs2_dir_t lfs2; + #endif + } dir; +} supervisor_vfs_dir_t; + +// Open a file on the given mount. path is relative to the mount. mtime_ns is +// the modification time to use for a newly written file, in nanoseconds past +// 1970/1/1, or 0 to use the current RTC time (the same time get_fattime() +// stamps FAT files with, including its fixed fallback when there is no RTC). On success, file must be closed with +// supervisor_vfs_close_file(). +supervisor_fs_err_t supervisor_vfs_open_file(supervisor_vfs_t *vfs, const char *path, uint32_t flags, + uint64_t mtime_ns, supervisor_vfs_file_t *file); +supervisor_fs_err_t supervisor_vfs_close_file(supervisor_vfs_file_t *file); +// Read at most len bytes into buf. On success *bytes_read holds the number of +// bytes read; it may be less than len, and 0 at end of file. +supervisor_fs_err_t supervisor_vfs_read_file(supervisor_vfs_file_t *file, void *buf, size_t len, size_t *bytes_read); +// Write len bytes. On success *bytes_written holds the number of bytes +// written; it is less than len when the filesystem is full. +supervisor_fs_err_t supervisor_vfs_write_file(supervisor_vfs_file_t *file, const void *buf, size_t len, size_t *bytes_written); +// Seek to an absolute offset from the start of the file. +supervisor_fs_err_t supervisor_vfs_seek_file(supervisor_vfs_file_t *file, size_t offset); +// Current absolute offset from the start of the file. +size_t supervisor_vfs_tell_file(supervisor_vfs_file_t *file); +// File size in bytes. +size_t supervisor_vfs_file_size(supervisor_vfs_file_t *file); +// Truncate the file to the current offset. +supervisor_fs_err_t supervisor_vfs_truncate_file(supervisor_vfs_file_t *file); + +// Stat a file or directory. All output arguments are optional (may be NULL). +// size is 0 for directories. mtime_ns is nanoseconds past the same epoch used +// for the littlefs mtime attribute (1970/1/1) and is 0 when unknown. +supervisor_fs_err_t supervisor_vfs_stat(supervisor_vfs_t *vfs, const char *path, bool *is_dir, size_t *size, uint64_t *mtime_ns); +// Create a directory. mtime_ns is used to stamp it on FAT, 0 to use the +// current RTC time. littlefs directories have no modification time. +supervisor_fs_err_t supervisor_vfs_mkdir(supervisor_vfs_t *vfs, const char *path, uint64_t mtime_ns); +supervisor_fs_err_t supervisor_vfs_rename(supervisor_vfs_t *vfs, const char *old_path, const char *new_path); +supervisor_fs_err_t supervisor_vfs_unlink(supervisor_vfs_t *vfs, const char *path); + +// Open a directory for listing. path is relative to the mount. +supervisor_fs_err_t supervisor_vfs_opendir(supervisor_vfs_t *vfs, const char *path, supervisor_vfs_dir_t *dir); +// Read the next directory entry. On success name holds a null-terminated entry +// name; at the end of the directory, name is empty. The other output arguments +// are optional (may be NULL). +supervisor_fs_err_t supervisor_vfs_readdir(supervisor_vfs_dir_t *dir, char *name, size_t name_len, bool *is_dir, size_t *size, uint64_t *mtime_ns); +supervisor_fs_err_t supervisor_vfs_rewinddir(supervisor_vfs_dir_t *dir); +supervisor_fs_err_t supervisor_vfs_closedir(supervisor_vfs_dir_t *dir); + +// Filesystem geometry in blocks. For FAT these are clusters; for littlefs, +// blocks. +supervisor_fs_err_t supervisor_vfs_statfs(supervisor_vfs_t *vfs, size_t *block_size, size_t *total_blocks, size_t *free_blocks); + void filesystem_background(void); void filesystem_tick(void); bool filesystem_init(bool create_allowed, bool force_create); @@ -19,30 +183,35 @@ void filesystem_flush(void); bool filesystem_present(void); void filesystem_set_internal_writable_by_usb(bool usb_writable); void filesystem_set_internal_concurrent_write_protection(bool concurrent_write_protection); -void filesystem_set_writable_by_usb(fs_user_mount_t *vfs, bool usb_writable); -void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concurrent_write_protection); +void filesystem_set_writable_by_usb(supervisor_vfs_t *vfs, bool usb_writable); +void filesystem_set_concurrent_write_protection(supervisor_vfs_t *vfs, bool concurrent_write_protection); void filesystem_set_ignore_write_protection(fs_user_mount_t *vfs, bool ignore_write_protection); // Whether user code can modify the filesystem. It doesn't depend on the state // of USB. Don't use this for a workflow. In workflows, grab the shared file // system lock. -bool filesystem_is_writable_by_python(fs_user_mount_t *vfs); +bool filesystem_is_writable_by_python(supervisor_vfs_t *vfs); // This controls whether USB tries to grab the underlying block device lock // during enumeration. If another workflow is modifying the filesystem when this // happens, then USB will be readonly. bool filesystem_is_writable_by_usb(fs_user_mount_t *vfs); -fs_user_mount_t *filesystem_circuitpy(void); -fs_user_mount_t *filesystem_for_path(const char *path_in, const char **path_under_mount); -bool filesystem_native_fatfs(fs_user_mount_t *fs_mount); +supervisor_vfs_t *filesystem_circuitpy(void); +supervisor_vfs_t *filesystem_for_path(const char *path_in, const char **path_under_mount); + +// Whether the supervisor-level filesystem API (above) can operate on this +// mount: FAT mounts reachable through FatFS and supervisor littlefs mounts. +// Use it to reject mounts (e.g. non-native or remote filesystems) that the +// workflows cannot access. +bool supervisor_vfs_supported(supervisor_vfs_t *fs_mount); // We have two levels of locking. filesystem_* calls grab a shared blockdev lock to allow // CircuitPython's fatfs code to edit the blocks. blockdev_* calls grab a lock to mutate blocks // directly, excluding any filesystem_* locks. -bool filesystem_lock(fs_user_mount_t *fs_mount); -void filesystem_unlock(fs_user_mount_t *fs_mount); +bool filesystem_lock(supervisor_vfs_t *fs_mount); +void filesystem_unlock(supervisor_vfs_t *fs_mount); -bool blockdev_lock(fs_user_mount_t *fs_mount); -void blockdev_unlock(fs_user_mount_t *fs_mount); +bool blockdev_lock(supervisor_vfs_t *fs_mount); +void blockdev_unlock(supervisor_vfs_t *fs_mount); diff --git a/supervisor/flash.h b/supervisor/flash.h index ca3e42735ff..cdd7eb4ca01 100644 --- a/supervisor/flash.h +++ b/supervisor/flash.h @@ -28,6 +28,7 @@ struct _fs_user_mount_t; void supervisor_flash_init_vfs(struct _fs_user_mount_t *vfs); void supervisor_flash_flush(void); void supervisor_flash_release_cache(void); +void supervisor_flash_mark_dirty(void); void supervisor_flash_set_extended(bool extended); bool supervisor_flash_get_extended(void); diff --git a/supervisor/port.h b/supervisor/port.h index 2437edafb14..3d927318a1c 100644 --- a/supervisor/port.h +++ b/supervisor/port.h @@ -115,6 +115,11 @@ void port_boot_info(void); // A default weak implementation is provided that does nothing. void port_gc_collect(void); +// Called once the VM heap is torn down and its finalisers have run. Ports free +// memory here that heap objects may point into. +// A default weak implementation is provided that does nothing. +void port_gc_deinit(void); + // Most ports that implement CIRCUITPY_BOOT_BUTTON use a generic version of // this function to sense the button. Ports that need to can override this // function to provide their own implementation. diff --git a/supervisor/shared/async_flag.h b/supervisor/shared/async_flag.h new file mode 100644 index 00000000000..5dd2c726386 --- /dev/null +++ b/supervisor/shared/async_flag.h @@ -0,0 +1,18 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +// Flag that marks an operation as done, possibly set from an interrupt. Ports may override these +// before this header is included. +#ifndef CIRCUITPY_ASYNC_FLAG_SET +typedef volatile bool circuitpy_async_flag_t; +#define CIRCUITPY_ASYNC_FLAG_INIT(flag) (*(flag) = false) +#define CIRCUITPY_ASYNC_FLAG_SET(flag) (*(flag) = true) +#define CIRCUITPY_ASYNC_FLAG_IS_SET(flag) (*(flag)) +#endif diff --git a/supervisor/shared/bluetooth/bluetooth.c b/supervisor/shared/bluetooth/bluetooth.c index 15cddd7d28c..e5b9c0eb290 100644 --- a/supervisor/shared/bluetooth/bluetooth.c +++ b/supervisor/shared/bluetooth/bluetooth.c @@ -232,13 +232,13 @@ void supervisor_bluetooth_init(void) { #if CIRCUITPY_STATUS_LED status_led_init(); #endif - uint64_t start_ticks = supervisor_ticks_ms64(); - uint64_t diff = 0; if (ble_mode != 0) { boot_in_discovery_mode = true; reset_state = 0x0; } bool bonded = common_hal_bleio_adapter_is_bonded_to_central(&common_hal_bleio_adapter_obj); + uint64_t start_ticks = supervisor_ticks_ms64(); + uint64_t diff = 0; // Don't go into discovery mode when waking from deep sleep. But if we're already bonded, // BLE workflow can continue after deep sleep. diff --git a/supervisor/shared/bluetooth/file_transfer.c b/supervisor/shared/bluetooth/file_transfer.c index d8b5f68ca2d..69566beaf34 100644 --- a/supervisor/shared/bluetooth/file_transfer.c +++ b/supervisor/shared/bluetooth/file_transfer.c @@ -14,7 +14,6 @@ #include "shared-bindings/_bleio/Service.h" #include "shared-bindings/_bleio/UUID.h" -#include "supervisor/fatfs.h" #include "supervisor/filesystem.h" #include "supervisor/shared/reload.h" #include "supervisor/shared/bluetooth/file_transfer.h" @@ -118,22 +117,15 @@ void supervisor_stop_bluetooth_file_transfer(void) { #define THIS_COMMAND 0x01 // FATFS has a two second timestamp resolution but the BLE API allows for nanosecond resolution. -// This function truncates the time the time to a resolution storable by FATFS and fills in the -// FATFS encoded version into fattime. -static uint64_t truncate_time(uint64_t input_time, DWORD *fattime) { - timeutils_struct_time_t tm; +// This function truncates the time to a resolution storable by FATFS and returns it. +static uint64_t truncate_time(uint64_t input_time) { uint64_t seconds_since_epoch = timeutils_seconds_since_epoch_from_nanoseconds_since_1970(input_time); - timeutils_seconds_since_epoch_to_struct_time(seconds_since_epoch, &tm); - uint64_t truncated_time = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970((seconds_since_epoch / 2) * 2 * 1000000000); - - *fattime = ((tm.tm_year - 1980) << 25) | (tm.tm_mon << 21) | (tm.tm_mday << 16) | - (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); - return truncated_time; + return timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970((seconds_since_epoch / 2) * 2 * 1000000000); } // Used by read and write. -static FIL active_file; -static fs_user_mount_t *active_mount; +static supervisor_vfs_file_t active_file; +static supervisor_vfs_t *active_mount; static uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) { struct read_command *command = (struct read_command *)raw_buf; size_t header_size = sizeof(struct read_command); @@ -158,20 +150,18 @@ static uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) { const char *mount_path; active_mount = filesystem_for_path(full_path, &mount_path); - if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { + if (active_mount == NULL || !supervisor_vfs_supported(active_mount)) { response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); return ANY_COMMAND; } - FATFS *fs = &active_mount->fatfs; - FRESULT result = f_open(fs, &active_file, mount_path, FA_READ); - if (result != FR_OK) { + if (supervisor_vfs_open_file(active_mount, mount_path, SUPERVISOR_FS_OPEN_READ, 0, &active_file) != SUPERVISOR_FS_OK) { response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); return ANY_COMMAND; } - uint32_t total_length = f_size(&active_file); + uint32_t total_length = supervisor_vfs_file_size(&active_file); // Write out the response header. uint32_t offset = command->chunk_offset; uint32_t chunk_size = command->chunk_size; @@ -180,20 +170,20 @@ static uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) { response.total_length = total_length; response.data_size = chunk_size; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); - f_lseek(&active_file, offset); + supervisor_vfs_seek_file(&active_file, offset); // Write out the chunk contents. We can do this in small pieces because PacketBuffer // will assemble them into larger packets of its own. size_t chunk_end = offset + chunk_size; while (offset < chunk_end) { size_t quantity_read; size_t read_amount = MIN(response_size, chunk_end - offset); - f_read(&active_file, data_buffer, read_amount, &quantity_read); + supervisor_vfs_read_file(&active_file, data_buffer, read_amount, &quantity_read); offset += quantity_read; // TODO: Do something if the read fails common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, data_buffer, quantity_read, NULL, 0); } if (offset >= total_length) { - f_close(&active_file); + supervisor_vfs_close_file(&active_file); return ANY_COMMAND; } return READ_PACING; @@ -206,14 +196,14 @@ static uint8_t _process_read_pacing(const uint8_t *raw_buf, size_t command_len) response.status = STATUS_OK; size_t response_size = sizeof(struct read_data); - uint32_t total_length = f_size(&active_file); + uint32_t total_length = supervisor_vfs_file_size(&active_file); // Write out the response header. uint32_t chunk_size = MIN(command->chunk_size, total_length - command->chunk_offset); response.chunk_offset = command->chunk_offset; response.total_length = total_length; response.data_size = chunk_size; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); - f_lseek(&active_file, command->chunk_offset); + supervisor_vfs_seek_file(&active_file, command->chunk_offset); // Write out the chunk contents. We can do this in small pieces because PacketBuffer // will assemble them into larger packets of its own. size_t chunk_offset = 0; @@ -221,8 +211,8 @@ static uint8_t _process_read_pacing(const uint8_t *raw_buf, size_t command_len) while (chunk_offset < chunk_size) { size_t quantity_read; size_t read_size = MIN(chunk_size - chunk_offset, sizeof(data)); - FRESULT result = f_read(&active_file, &data, read_size, &quantity_read); - if (quantity_read == 0 || result != FR_OK) { + supervisor_fs_err_t result = supervisor_vfs_read_file(&active_file, &data, read_size, &quantity_read); + if (quantity_read == 0 || result != SUPERVISOR_FS_OK) { // TODO: If we can't read everything, then the file must have been shortened. Maybe we // should return 0s to pad it out. break; @@ -231,7 +221,7 @@ static uint8_t _process_read_pacing(const uint8_t *raw_buf, size_t command_len) chunk_offset += quantity_read; } if ((chunk_offset + chunk_size) >= total_length) { - f_close(&active_file); + supervisor_vfs_close_file(&active_file); return ANY_COMMAND; } return READ_PACING; @@ -264,7 +254,7 @@ static uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { const char *mount_path; active_mount = filesystem_for_path(full_path, &mount_path); - if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { + if (active_mount == NULL || !supervisor_vfs_supported(active_mount)) { response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); return ANY_COMMAND; @@ -275,16 +265,13 @@ static uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { return ANY_COMMAND; } - FATFS *fs = &active_mount->fatfs; - DWORD fattime; - _truncated_time = truncate_time(command->modification_time, &fattime); - override_fattime(fattime); - FRESULT result = f_open(fs, &active_file, mount_path, FA_WRITE | FA_OPEN_ALWAYS); - if (result != FR_OK) { + _truncated_time = truncate_time(command->modification_time); + supervisor_fs_err_t result = supervisor_vfs_open_file(active_mount, mount_path, + SUPERVISOR_FS_OPEN_WRITE | SUPERVISOR_FS_OPEN_CREATE, command->modification_time, &active_file); + if (result != SUPERVISOR_FS_OK) { response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); filesystem_unlock(active_mount); - override_fattime(0); return ANY_COMMAND; } // Write out the pacing response. @@ -294,10 +281,9 @@ static uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { size_t chunk_size = MIN(total_write_length - offset, 512 - (offset % 512)); // Special case when truncating the file. (Deleting stuff off the end.) if (chunk_size == 0) { - f_lseek(&active_file, offset); - f_truncate(&active_file); - f_close(&active_file); - override_fattime(0); + supervisor_vfs_seek_file(&active_file, offset); + supervisor_vfs_truncate_file(&active_file); + supervisor_vfs_close_file(&active_file); filesystem_unlock(active_mount); } response.offset = offset; @@ -324,7 +310,6 @@ static uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); filesystem_unlock(active_mount); - override_fattime(0); return ANY_COMMAND; } // We need to receive another packet to have the full path. @@ -332,15 +317,14 @@ static uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { return THIS_COMMAND; } uint32_t offset = command->offset; - f_lseek(&active_file, offset); - UINT actual; - f_write(&active_file, command->data, command->data_size, &actual); - if (actual < command->data_size) { // -1 for the null we'll write + supervisor_vfs_seek_file(&active_file, offset); + size_t actual; + supervisor_fs_err_t result = supervisor_vfs_write_file(&active_file, command->data, command->data_size, &actual); + if (actual < command->data_size || result != SUPERVISOR_FS_OK) { // -1 for the null we'll write // TODO: throw away any more packets of path. response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); filesystem_unlock(active_mount); - override_fattime(0); return ANY_COMMAND; } offset += command->data_size; @@ -351,9 +335,8 @@ static uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { response.truncated_time = _truncated_time; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); if (total_write_length == offset) { - f_truncate(&active_file); - f_close(&active_file); - override_fattime(0); + supervisor_vfs_truncate_file(&active_file); + supervisor_vfs_close_file(&active_file); filesystem_unlock(active_mount); // Don't reload until everything is written out of the packet buffer. common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); @@ -382,16 +365,16 @@ static uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { char *full_path = (char *)((uint8_t *)command) + header_size; full_path[command->path_length] = '\0'; - FRESULT result = supervisor_workflow_delete_recursive(full_path); + supervisor_fs_err_t result = supervisor_workflow_delete_recursive(full_path); - if (result == FR_WRITE_PROTECTED) { + if (result == SUPERVISOR_FS_WRITE_PROTECTED) { response.status = STATUS_ERROR_READONLY; } - if (result != FR_OK) { + if (result != SUPERVISOR_FS_OK) { response.status = STATUS_ERROR; } common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); - if (result == FR_OK) { + if (result == SUPERVISOR_FS_OK) { // Don't reload until everything is written out of the packet buffer. common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); } @@ -399,10 +382,11 @@ static uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { } // NULL-terminate the path and remove any trailing /. Older versions of the -// protocol require it but newer ones do not. +// protocol require it but newer ones do not. Keep the trailing / for the +// root: both FatFS and littlefs accept "/", but FatFS treats "" as the +// volume's current directory, not the root. static void _terminate_path(char *path, size_t path_length) { - // -1 because fatfs doesn't want a trailing / - if (path[path_length - 1] == '/') { + if (path_length > 1 && path[path_length - 1] == '/') { path[path_length - 1] = '\0'; } else { path[path_length] = '\0'; @@ -428,14 +412,12 @@ static uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { char *full_path = (char *)command->path; _terminate_path(full_path, command->path_length); - DWORD fattime; - response.truncated_time = truncate_time(command->modification_time, &fattime); - FRESULT result = supervisor_workflow_mkdir(fattime, full_path); - if (result != FR_OK) { + supervisor_fs_err_t result = supervisor_workflow_mkdir(truncate_time(command->modification_time), full_path); + if (result != SUPERVISOR_FS_OK) { response.status = STATUS_ERROR; } common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); - if (result == FR_OK) { + if (result == SUPERVISOR_FS_OK) { // Don't reload until everything is written out of the packet buffer. common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); } @@ -457,6 +439,9 @@ static uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { const struct listdir_command *command = (struct listdir_command *)raw_buf; struct listdir_entry *entry = (struct listdir_entry *)raw_buf; size_t header_size = sizeof(struct listdir_command); + bool entry_is_dir; + size_t entry_size; + uint64_t entry_mtime; mp_int_t max_packet_size = common_hal_bleio_packet_buffer_get_outgoing_packet_length(&_transfer_packet_buffer); if (max_packet_size < 0) { // -1 means we're disconnected @@ -481,16 +466,16 @@ static uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { const char *mount_path; active_mount = filesystem_for_path(full_path, &mount_path); - if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { + if (active_mount == NULL || !supervisor_vfs_supported(active_mount)) { entry->command = LISTDIR_ENTRY; entry->status = STATUS_ERROR_NO_FILE; send_listdir_entry_header(entry, max_packet_size); return ANY_COMMAND; } - FATFS *fs = &active_mount->fatfs; - FF_DIR dir; - FRESULT res = f_opendir(fs, &dir, mount_path); + supervisor_vfs_dir_t dir; + memset(&dir, 0, sizeof(dir)); + supervisor_fs_err_t res = supervisor_vfs_opendir(active_mount, mount_path, &dir); entry->command = LISTDIR_ENTRY; entry->status = STATUS_OK; @@ -499,51 +484,44 @@ static uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { entry->entry_count = 0; entry->flags = 0; - if (res != FR_OK) { + if (res != SUPERVISOR_FS_OK) { entry->status = STATUS_ERROR_NO_FILE; send_listdir_entry_header(entry, max_packet_size); return ANY_COMMAND; } - FILINFO file_info; - res = f_readdir(&dir, &file_info); - char *fn = file_info.fname; + char fn[FF_MAX_LFN + 1]; + res = supervisor_vfs_readdir(&dir, fn, sizeof(fn), NULL, NULL, NULL); size_t total_entries = 0; - while (res == FR_OK && fn[0] != 0) { - res = f_readdir(&dir, &file_info); + while (res == SUPERVISOR_FS_OK && fn[0] != 0) { + res = supervisor_vfs_readdir(&dir, fn, sizeof(fn), NULL, NULL, NULL); total_entries += 1; } // Rewind the directory. - f_readdir(&dir, NULL); + supervisor_vfs_rewinddir(&dir); entry->entry_count = total_entries; for (size_t i = 0; i < total_entries; i++) { - res = f_readdir(&dir, &file_info); + res = supervisor_vfs_readdir(&dir, fn, sizeof(fn), &entry_is_dir, &entry_size, &entry_mtime); entry->entry_number = i; - uint64_t truncated_time = timeutils_mktime(1980 + (file_info.fdate >> 9), - (file_info.fdate >> 5) & 0xf, - file_info.fdate & 0x1f, - file_info.ftime >> 11, - (file_info.ftime >> 5) & 0x1f, - (file_info.ftime & 0x1f) * 2) * 1000000000ULL; - entry->truncated_time = truncated_time; - if ((file_info.fattrib & AM_DIR) != 0) { + entry->truncated_time = entry_mtime; + if (entry_is_dir) { entry->flags = 1; // Directory entry->file_size = 0; } else { entry->flags = 0; - entry->file_size = file_info.fsize; + entry->file_size = entry_size; } - size_t name_length = strlen(file_info.fname); + size_t name_length = strlen(fn); entry->path_length = name_length; send_listdir_entry_header(entry, max_packet_size); size_t fn_offset = 0; while (fn_offset < name_length) { size_t fn_size = MIN(name_length - fn_offset, 4); - common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, ((uint8_t *)file_info.fname) + fn_offset, fn_size, NULL, 0); + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, ((uint8_t *)fn) + fn_offset, fn_size, NULL, 0); fn_offset += fn_size; } } - f_closedir(&dir); + supervisor_vfs_closedir(&dir); entry->path_length = 0; entry->entry_number = entry->entry_count; entry->flags = 0; @@ -577,14 +555,14 @@ static uint8_t _process_move(const uint8_t *raw_buf, size_t command_len) { char *new_path = old_path + command->old_path_length + 1; new_path[command->new_path_length] = '\0'; - FRESULT result = supervisor_workflow_move(old_path, new_path); - if (result == FR_WRITE_PROTECTED) { + supervisor_fs_err_t result = supervisor_workflow_move(old_path, new_path); + if (result == SUPERVISOR_FS_WRITE_PROTECTED) { response.status = STATUS_ERROR_READONLY; - } else if (result != FR_OK) { + } else if (result != SUPERVISOR_FS_OK) { response.status = STATUS_ERROR; } common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); - if (result == FR_OK) { + if (result == SUPERVISOR_FS_OK) { // Don't reload until everything is written out of the packet buffer. common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); } @@ -678,6 +656,6 @@ void supervisor_bluetooth_file_transfer_background(void) { void supervisor_bluetooth_file_transfer_disconnected(void) { next_command = ANY_COMMAND; current_offset = 0; - f_close(&active_file); + supervisor_vfs_close_file(&active_file); autoreload_resume(AUTORELOAD_SUSPEND_BLE); } diff --git a/supervisor/shared/display.c b/supervisor/shared/display.c index 1352c76b006..8ed86de42c8 100644 --- a/supervisor/shared/display.c +++ b/supervisor/shared/display.c @@ -46,8 +46,6 @@ #if CIRCUITPY_LVFONTIO #include "shared-bindings/lvfontio/OnDiskFont.h" #include "supervisor/filesystem.h" -#include "extmod/vfs_fat.h" -#include "lib/oofatfs/ff.h" #include "supervisor/shared/serial.h" @@ -58,13 +56,13 @@ static bool check_for_custom_font(const char **font_path_out) { return false; } - fs_user_mount_t *vfs = filesystem_circuitpy(); + // Check for the font through the supervisor filesystem + // API so it works on FAT and littlefs mounts. + supervisor_vfs_t *vfs = filesystem_circuitpy(); if (vfs == NULL) { return false; } - // Use FATFS directly to check if file exists - FILINFO file_info; const char *default_font_path = "/fonts/terminal.lvfontbin"; const char *font_path = default_font_path; @@ -77,8 +75,7 @@ static bool check_for_custom_font(const char **font_path_out) { } #endif - FRESULT result = f_stat(&vfs->fatfs, font_path, &file_info); - if (result == FR_OK) { + if (supervisor_vfs_stat(vfs, font_path, NULL, NULL, NULL) == SUPERVISOR_FS_OK) { if (font_path_out != NULL) { *font_path_out = font_path; } @@ -87,9 +84,8 @@ static bool check_for_custom_font(const char **font_path_out) { // If custom font path doesn't exist, use default font font_path = default_font_path; - result = f_stat(&vfs->fatfs, font_path, &file_info); - if (result == FR_OK) { + if (supervisor_vfs_stat(vfs, font_path, NULL, NULL, NULL) == SUPERVISOR_FS_OK) { if (font_path_out != NULL) { *font_path_out = font_path; } diff --git a/supervisor/shared/external_flash/spi_flash.c b/supervisor/shared/external_flash/spi_flash.c index f9ed50ac46f..f9ab068e591 100644 --- a/supervisor/shared/external_flash/spi_flash.c +++ b/supervisor/shared/external_flash/spi_flash.c @@ -126,11 +126,15 @@ void spi_flash_init(void) { // Set CS high (disabled). common_hal_digitalio_digitalinout_switch_to_output(&cs_pin, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_digitalio_digitalinout_never_reset(&cs_pin); + #endif supervisor_flash_spi_bus.base.type = &busio_spi_type; common_hal_busio_spi_construct(&supervisor_flash_spi_bus, SPI_FLASH_SCK_PIN, SPI_FLASH_MOSI_PIN, SPI_FLASH_MISO_PIN, false); + #if CIRCUITPY_BULK_RESET common_hal_busio_spi_never_reset(&supervisor_flash_spi_bus); + #endif common_hal_busio_spi_configure(&supervisor_flash_spi_bus, SPI_FLASH_MAX_BAUDRATE, 0, 0, 8); return; diff --git a/supervisor/shared/fatfs.c b/supervisor/shared/fatfs.c index 07ab724d7a9..ba437158f73 100644 --- a/supervisor/shared/fatfs.c +++ b/supervisor/shared/fatfs.c @@ -15,6 +15,21 @@ #include "shared/timeutils/timeutils.h" DWORD _time_override = 0; + +// 2016-09-01 16:43:35 UTC, the fallback timestamp used when there is no RTC. +#define FATTIME_FALLBACK_NS (1472748215000000000ULL) + +uint64_t get_fattime_ns(void) { + #if CIRCUITPY_RTC + timeutils_struct_time_t tm; + common_hal_rtc_get_time(&tm); + return (uint64_t)timeutils_mktime_1970(tm.tm_year, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec) * 1000000000ULL; + #else + return FATTIME_FALLBACK_NS; + #endif +} + DWORD get_fattime(void) { if (_time_override > 0) { return _time_override; @@ -25,6 +40,7 @@ DWORD get_fattime(void) { return ((tm.tm_year - 1980) << 25) | (tm.tm_mon << 21) | (tm.tm_mday << 16) | (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); #else + // Same instant as FATTIME_FALLBACK_NS: 2016-09-01 16:43:35 UTC. return ((2016 - 1980) << 25) | ((9) << 21) | ((1) << 16) | ((16) << 11) | ((43) << 5) | (35 / 2); #endif } diff --git a/supervisor/shared/filesystem.c b/supervisor/shared/filesystem.c index 4335e9afc6e..081145e7de5 100644 --- a/supervisor/shared/filesystem.c +++ b/supervisor/shared/filesystem.c @@ -12,9 +12,18 @@ #include "py/mpstate.h" +#include "shared/timeutils/timeutils.h" +#include "supervisor/fatfs.h" #include "supervisor/flash.h" #include "supervisor/linker.h" +#if CIRCUITPY_FILESYSTEM_LITTLEFS +#include "extmod/vfs_lfs.h" +#include "lib/littlefs/lfs2.h" +#include "supervisor/port_heap.h" +#include "supervisor/shared/tick.h" +#endif + #if CIRCUITPY_SDCARDIO #include "shared-module/sdcardio/__init__.h" #endif @@ -24,7 +33,11 @@ #endif static mp_vfs_mount_t _circuitpy_vfs; -static fs_user_mount_t _circuitpy_usermount; +static supervisor_vfs_t _circuitpy_mount; + +#if CIRCUITPY_FILESYSTEM_LITTLEFS +static bool _lfs_freshly_formatted; +#endif #if CIRCUITPY_SAVES_PARTITION_SIZE > 0 static mp_vfs_mount_t _saves_vfs; @@ -34,6 +47,97 @@ static fs_user_mount_t _saves_usermount; static volatile uint32_t filesystem_flush_interval_ms = CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS; volatile bool filesystem_flush_requested = false; +// Mapping between FatFS and littlefs results and the supervisor filesystem +// error codes. +#if CIRCUITPY_FILESYSTEM_LITTLEFS +static supervisor_fs_err_t fat_error(FRESULT res) { + switch (res) { + case FR_OK: + return SUPERVISOR_FS_OK; + case FR_NO_FILE: + return SUPERVISOR_FS_NO_FILE; + case FR_NO_PATH: + case FR_INVALID_NAME: + return SUPERVISOR_FS_NO_PATH; + case FR_EXIST: + return SUPERVISOR_FS_EXIST; + case FR_WRITE_PROTECTED: + return SUPERVISOR_FS_WRITE_PROTECTED; + case FR_DENIED: + // FatFS uses FR_DENIED for read-only media and for no free + // cluster. We can't tell them apart, so report no space. + return SUPERVISOR_FS_NO_SPACE; + default: + return SUPERVISOR_FS_IO; + } +} +#else +// On FAT-only builds supervisor_fs_err_t is FRESULT, so +// FatFS results need no translation. This keeps non-littlefs builds from +// growing when the supervisor filesystem API was added. +#define fat_error(res) ((supervisor_fs_err_t)(res)) +#endif + +// Convert nanoseconds past 1970/1/1 to the FAT timestamp format. +static DWORD fattime_from_ns(uint64_t ns) { + timeutils_struct_time_t tm; + uint64_t seconds = timeutils_seconds_since_epoch_from_nanoseconds_since_1970(ns); + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); + return ((tm.tm_year - 1980) << 25) | (tm.tm_mon << 21) | (tm.tm_mday << 16) | + (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); +} + +#if CIRCUITPY_FILESYSTEM_LITTLEFS +static supervisor_fs_err_t lfs_error(int err) { + switch (err) { + case LFS2_ERR_OK: + return SUPERVISOR_FS_OK; + case LFS2_ERR_NOENT: + return SUPERVISOR_FS_NO_FILE; + case LFS2_ERR_EXIST: + return SUPERVISOR_FS_EXIST; + case LFS2_ERR_NOTDIR: + case LFS2_ERR_ISDIR: + return SUPERVISOR_FS_NO_PATH; + case LFS2_ERR_NOSPC: + return SUPERVISOR_FS_NO_SPACE; + default: + return SUPERVISOR_FS_IO; + } +} + +// Store nanoseconds past the littlefs mtime epoch (1970/1/1) little-endian. +static void lfs_mtime_store(uint8_t buf[8], uint64_t ns) { + ns = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(ns); + for (size_t i = 0; i < 8; ++i) { + buf[i] = ns; + ns >>= 8; + } +} + +static uint64_t lfs_mtime_load(const uint8_t buf[8]) { + uint64_t ns = 0; + for (size_t i = 8; i > 0; --i) { + ns = ns << 8 | buf[i - 1]; + } + return timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(ns); +} + +// Read a file or directory modification time from the littlefs mtime attribute. +static uint64_t lfs_get_path_mtime(lfs2_t *lfs, const char *path) { + uint8_t mtime[8]; + lfs2_ssize_t sz = lfs2_getattr(lfs, path, LFS_ATTR_MTIME, mtime, sizeof(mtime)); + if (sz != (lfs2_ssize_t)sizeof(mtime)) { + return 0; + } + return lfs_mtime_load(mtime); +} + +static bool is_lfs(supervisor_vfs_t *vfs) { + return vfs->common.base.type == &mp_type_vfs_lfs2; +} +#endif + void filesystem_background(void) { if (filesystem_flush_requested) { filesystem_flush_interval_ms = CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS; @@ -56,37 +160,177 @@ inline void filesystem_tick(void) { } } - __attribute__((unused)) // this function MAY be unused -static void make_empty_file(FATFS *fatfs, const char *path) { - FIL fp; - f_open(fatfs, &fp, path, FA_WRITE | FA_CREATE_ALWAYS); - f_close(&fp); +static bool make_empty_file(supervisor_vfs_t *vfs, const char *path) { + supervisor_vfs_file_t fp; + supervisor_fs_err_t err = supervisor_vfs_open_file(vfs, path, + SUPERVISOR_FS_OPEN_WRITE | SUPERVISOR_FS_OPEN_CREATE | SUPERVISOR_FS_OPEN_TRUNCATE, 0, &fp); + if (err != SUPERVISOR_FS_OK) { + return false; + } + return supervisor_vfs_close_file(&fp) == SUPERVISOR_FS_OK; } #if CIRCUITPY_FULL_BUILD -#define MAKE_FILE_WITH_OPTIONAL_CONTENTS(fatfs, filename, string_literal) do { \ +#define MAKE_FILE_WITH_OPTIONAL_CONTENTS(vfs, filename, string_literal) do { \ const byte buffer[] = string_literal; \ - make_file_with_contents(fatfs, filename, buffer, sizeof(buffer) - 1); \ + make_file_with_contents(vfs, filename, buffer, sizeof(buffer) - 1); \ } while (0) -static void make_file_with_contents(FATFS *fatfs, const char *filename, const byte *content, UINT size) { - FIL fs; - // Create or modify existing code.py file - f_open(fatfs, &fs, filename, FA_WRITE | FA_CREATE_ALWAYS); - f_write(&fs, content, size, &size); - f_close(&fs); +static bool make_file_with_contents(supervisor_vfs_t *vfs, const char *filename, const byte *content, UINT size) { + supervisor_vfs_file_t fp; + if (supervisor_vfs_open_file(vfs, filename, + SUPERVISOR_FS_OPEN_WRITE | SUPERVISOR_FS_OPEN_CREATE | SUPERVISOR_FS_OPEN_TRUNCATE, 0, &fp) != SUPERVISOR_FS_OK) { + return false; + } + size_t written; + supervisor_vfs_write_file(&fp, content, size, &written); + return supervisor_vfs_close_file(&fp) == SUPERVISOR_FS_OK && written == size; } #else -#define MAKE_FILE_WITH_OPTIONAL_CONTENTS(fatfs, filename, string_literal) \ - make_empty_file(fatfs, filename) +#define MAKE_FILE_WITH_OPTIONAL_CONTENTS(vfs, filename, string_literal) \ + (void)make_empty_file(vfs, filename) #endif +#if CIRCUITPY_FILESYSTEM_LITTLEFS + +// littlefs block-device adapter for the supervisor flash block device. The +// block size is FILESYSTEM_BLOCK_SIZE so lfs2 blocks map 1:1 onto supervisor +// flash blocks. Reads/writes that are not block-aligned are served through a +// read-modify-write because the supervisor block device is block granular. + +static int lfs_flash_read(const struct lfs2_config *c, lfs2_block_t block, lfs2_off_t off, void *buffer, lfs2_size_t size) { + if (off == 0 && size == FILESYSTEM_BLOCK_SIZE) { + return supervisor_flash_read_blocks(buffer, block, 1) == 0 ? LFS2_ERR_OK : LFS2_ERR_IO; + } + uint8_t tmp[FILESYSTEM_BLOCK_SIZE]; + if (supervisor_flash_read_blocks(tmp, block, 1) != 0) { + return LFS2_ERR_IO; + } + memcpy(buffer, tmp + off, size); + return LFS2_ERR_OK; +} + +static int lfs_flash_prog(const struct lfs2_config *c, lfs2_block_t block, lfs2_off_t off, const void *buffer, lfs2_size_t size) { + supervisor_flash_mark_dirty(); + if (off == 0 && size == FILESYSTEM_BLOCK_SIZE) { + return supervisor_flash_write_blocks(buffer, block, 1) == 0 ? LFS2_ERR_OK : LFS2_ERR_IO; + } + uint8_t tmp[FILESYSTEM_BLOCK_SIZE]; + if (supervisor_flash_read_blocks(tmp, block, 1) != 0) { + return LFS2_ERR_IO; + } + memcpy(tmp + off, buffer, size); + if (supervisor_flash_write_blocks(tmp, block, 1) != 0) { + return LFS2_ERR_IO; + } + return LFS2_ERR_OK; +} + +// The supervisor block device erases as part of its write path, so there is +// nothing to do here. +static int lfs_flash_erase(const struct lfs2_config *c, lfs2_block_t block) { + return LFS2_ERR_OK; +} + +static int lfs_flash_sync(const struct lfs2_config *c) { + supervisor_flash_flush(); + return LFS2_ERR_OK; +} + +static void *lfs_supervisor_alloc(size_t size) { + return port_malloc(size, false); +} + +static bool lfs_write_boot_file(const char *path, const char *contents, size_t size) { + supervisor_vfs_file_t file; + if (supervisor_vfs_open_file(&_circuitpy_mount, path, + SUPERVISOR_FS_OPEN_WRITE | SUPERVISOR_FS_OPEN_CREATE | SUPERVISOR_FS_OPEN_TRUNCATE, 0, &file) != SUPERVISOR_FS_OK) { + return false; + } + size_t written = 0; + supervisor_vfs_write_file(&file, contents, size, &written); + supervisor_fs_err_t err = supervisor_vfs_close_file(&file); + return written == size && err == SUPERVISOR_FS_OK; +} + +#endif // CIRCUITPY_FILESYSTEM_LITTLEFS + // we don't make this function static because it needs a lot of stack and we // want it to be executed without using stack within main() function bool filesystem_init(bool create_allowed, bool force_create) { + mp_vfs_mount_t *circuitpy_vfs = &_circuitpy_vfs; + circuitpy_vfs->len = 0; + + #if CIRCUITPY_FILESYSTEM_LITTLEFS + // init the vfs object. The VM (and its GC) is not running yet, so + // everything here uses static storage or the port allocator. + mp_obj_vfs_lfs2_t *circuitpy = &_circuitpy_mount.lfs2; + memset(circuitpy, 0, sizeof(*circuitpy)); + circuitpy->base.type = &mp_type_vfs_lfs2; + // cur_dir is used to join relative paths (make_path). It cannot be + // allocated on the GC heap because the VM is not running yet, so give it + // a static buffer. It is truncated back to its previous length after each + // join, so it stays short in practice. + static char lfs_cur_dir_buf[128]; + vstr_init_fixed_buf(&circuitpy->cur_dir, sizeof(lfs_cur_dir_buf), lfs_cur_dir_buf); + vstr_add_str(&circuitpy->cur_dir, "/"); + circuitpy->enable_mtime = true; + + struct lfs2_config *config = &circuitpy->config; + memset(config, 0, sizeof(*config)); + config->context = circuitpy; + config->read = lfs_flash_read; + config->prog = lfs_flash_prog; + config->erase = lfs_flash_erase; + config->sync = lfs_flash_sync; + config->block_size = supervisor_flash_get_block_size(); + // Initialize the block device (opens the partition) before asking for its + // geometry; the FAT path does the same via the blockdev INIT ioctl. + supervisor_flash_init(); + config->block_count = supervisor_flash_get_block_count(); + config->read_size = config->block_size; + config->prog_size = config->block_size; + + _lfs_freshly_formatted = false; + if (force_create) { + // Reformat requested (e.g. from safe mode). + if (lfs2_format(&circuitpy->lfs, config) != LFS2_ERR_OK) { + return false; + } + _lfs_freshly_formatted = true; + } + int mount_err; + mp_obj_t mounted = mp_vfs_lfs2_mount_supervisor(circuitpy, lfs_supervisor_alloc, + create_allowed && !force_create, &_lfs_freshly_formatted, &mount_err); + if (mounted == MP_OBJ_NULL) { + return false; + } + (void)mount_err; + + if (_lfs_freshly_formatted) { + #if CIRCUITPY_SDCARDIO + lfs2_mkdir(&circuitpy->lfs, "/sd"); + #endif + + #if CIRCUITPY_SETTINGS_TOML + // settings.toml is not read yet on littlefs filesystems; see + // supervisor/shared/settings.c. Create it empty for forward + // compatibility. + lfs_write_boot_file("/settings.toml", "", 0); + #endif + // make a sample code.py file + lfs_write_boot_file("/code.py", "print(\"Hello World!\")\n", sizeof("print(\"Hello World!\")\n") - 1); + + // create empty lib directory + lfs2_mkdir(&circuitpy->lfs, "/lib"); + + // and ensure everything is flushed + supervisor_flash_flush(); + } + #else // init the vfs object - fs_user_mount_t *circuitpy = &_circuitpy_usermount; + fs_user_mount_t *circuitpy = &_circuitpy_mount.fat; circuitpy->blockdev.flags = 0; supervisor_flash_init_vfs(circuitpy); @@ -100,13 +344,10 @@ bool filesystem_init(bool create_allowed, bool force_create) { saves->blockdev.offset = 0; saves->blockdev.size = CIRCUITPY_SAVES_PARTITION_SIZE; supervisor_flash_init_vfs(saves); - filesystem_set_concurrent_write_protection(saves, true); - filesystem_set_writable_by_usb(saves, false); + filesystem_set_concurrent_write_protection((supervisor_vfs_t *)saves, true); + filesystem_set_writable_by_usb((supervisor_vfs_t *)saves, false); #endif - mp_vfs_mount_t *circuitpy_vfs = &_circuitpy_vfs; - circuitpy_vfs->len = 0; - // try to mount the flash FRESULT res = f_mount(&circuitpy->fatfs); if ((res == FR_NO_FILESYSTEM && create_allowed) || force_create) { @@ -139,19 +380,19 @@ bool filesystem_init(bool create_allowed, bool force_create) { if (res != FR_OK) { return false; } - make_empty_file(&circuitpy->fatfs, "/.fseventsd/no_log"); - make_empty_file(&circuitpy->fatfs, "/.metadata_never_index"); + make_empty_file(&_circuitpy_mount, "/.fseventsd/no_log"); + make_empty_file(&_circuitpy_mount, "/.metadata_never_index"); // Prevent storing trash on all OSes. - make_empty_file(&circuitpy->fatfs, "/.Trashes"); // MacOS - make_empty_file(&circuitpy->fatfs, "/.Trash-1000"); // Linux, XDG trash spec: + make_empty_file(&_circuitpy_mount, "/.Trashes"); // MacOS + make_empty_file(&_circuitpy_mount, "/.Trash-1000"); // Linux, XDG trash spec: // https://specifications.freedesktop.org/trash-spec/trashspec-latest.html #endif #if CIRCUITPY_SDCARDIO || CIRCUITPY_SDIOIO res = f_mkdir(&circuitpy->fatfs, "/sd"); #if CIRCUITPY_FULL_BUILD - MAKE_FILE_WITH_OPTIONAL_CONTENTS(&circuitpy->fatfs, "/sd/placeholder.txt", + MAKE_FILE_WITH_OPTIONAL_CONTENTS(&_circuitpy_mount, "/sd/placeholder.txt", "SD cards mounted at /sd will hide this file from Python.\n"); #endif #endif @@ -169,7 +410,7 @@ bool filesystem_init(bool create_allowed, bool force_create) { } #if CIRCUITPY_FULL_BUILD if (res == FR_OK) { - MAKE_FILE_WITH_OPTIONAL_CONTENTS(&circuitpy->fatfs, "/saves/placeholder.txt", + MAKE_FILE_WITH_OPTIONAL_CONTENTS(&_circuitpy_mount, "/saves/placeholder.txt", "A separate filesystem mounted at /saves will hide this file from Python." " Saves are visible via USB CPSAVES.\n"); } @@ -177,10 +418,10 @@ bool filesystem_init(bool create_allowed, bool force_create) { #endif #if CIRCUITPY_SETTINGS_TOML - make_empty_file(&circuitpy->fatfs, "/settings.toml"); + make_empty_file(&_circuitpy_mount, "/settings.toml"); #endif // make a sample code.py file - MAKE_FILE_WITH_OPTIONAL_CONTENTS(&circuitpy->fatfs, "/code.py", "print(\"Hello World!\")\n"); + MAKE_FILE_WITH_OPTIONAL_CONTENTS(&_circuitpy_mount, "/code.py", "print(\"Hello World!\")\n"); // create empty lib directory res = f_mkdir(&circuitpy->fatfs, "/lib"); @@ -193,6 +434,7 @@ bool filesystem_init(bool create_allowed, bool force_create) { } else if (res != FR_OK) { return false; } + #endif // CIRCUITPY_FILESYSTEM_LITTLEFS circuitpy_vfs->str = "/"; circuitpy_vfs->len = 1; @@ -253,23 +495,21 @@ void PLACE_IN_ITCM(filesystem_flush)(void) { } void filesystem_set_internal_writable_by_usb(bool writable) { - fs_user_mount_t *vfs = &_circuitpy_usermount; - - filesystem_set_writable_by_usb(vfs, writable); + filesystem_set_writable_by_usb(&_circuitpy_mount, writable); } -void filesystem_set_writable_by_usb(fs_user_mount_t *vfs, bool usb_writable) { +void filesystem_set_writable_by_usb(supervisor_vfs_t *vfs, bool usb_writable) { if (usb_writable) { - vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_USB_WRITABLE; + vfs->common.blockdev.flags |= MP_BLOCKDEV_FLAG_USB_WRITABLE; } else { - vfs->blockdev.flags &= ~MP_BLOCKDEV_FLAG_USB_WRITABLE; + vfs->common.blockdev.flags &= ~MP_BLOCKDEV_FLAG_USB_WRITABLE; } } -bool filesystem_is_writable_by_python(fs_user_mount_t *vfs) { - return ((vfs->blockdev.flags & MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED) == 0) || - ((vfs->blockdev.flags & MP_BLOCKDEV_FLAG_USB_WRITABLE) == 0) || - ((vfs->blockdev.flags & MP_BLOCKDEV_FLAG_IGNORE_WRITE_PROTECTION) != 0); +bool filesystem_is_writable_by_python(supervisor_vfs_t *vfs) { + return ((vfs->common.blockdev.flags & MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED) == 0) || + ((vfs->common.blockdev.flags & MP_BLOCKDEV_FLAG_USB_WRITABLE) == 0) || + ((vfs->common.blockdev.flags & MP_BLOCKDEV_FLAG_IGNORE_WRITE_PROTECTION) != 0); } bool filesystem_is_writable_by_usb(fs_user_mount_t *vfs) { @@ -279,14 +519,14 @@ bool filesystem_is_writable_by_usb(fs_user_mount_t *vfs) { } void filesystem_set_internal_concurrent_write_protection(bool concurrent_write_protection) { - filesystem_set_concurrent_write_protection(&_circuitpy_usermount, concurrent_write_protection); + filesystem_set_concurrent_write_protection(&_circuitpy_mount, concurrent_write_protection); } -void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concurrent_write_protection) { +void filesystem_set_concurrent_write_protection(supervisor_vfs_t *vfs, bool concurrent_write_protection) { if (concurrent_write_protection) { - vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED; + vfs->common.blockdev.flags |= MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED; } else { - vfs->blockdev.flags &= ~MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED; + vfs->common.blockdev.flags &= ~MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED; } } @@ -302,24 +542,24 @@ bool filesystem_present(void) { return _circuitpy_vfs.len > 0; } -fs_user_mount_t *filesystem_circuitpy(void) { +supervisor_vfs_t *filesystem_circuitpy(void) { if (!filesystem_present()) { return NULL; } - return &_circuitpy_usermount; + return &_circuitpy_mount; } -fs_user_mount_t *filesystem_for_path(const char *path_in, const char **path_under_mount) { +supervisor_vfs_t *filesystem_for_path(const char *path_in, const char **path_under_mount) { mp_vfs_mount_t *vfs = mp_vfs_lookup_path(path_in, path_under_mount); if (vfs == MP_VFS_NONE) { return NULL; } - fs_user_mount_t *fs_mount; + supervisor_vfs_t *fs_mount; *path_under_mount = path_in; if (vfs == MP_VFS_ROOT) { fs_mount = filesystem_circuitpy(); } else { - fs_mount = MP_OBJ_TO_PTR(vfs->obj); + fs_mount = (supervisor_vfs_t *)MP_OBJ_TO_PTR(vfs->obj); // Check if the vfs name is one character long: it must be "/" in that case. // If so don't remove the mount point name. We must use an absolute path // because otherwise the path will be adjusted by os.getcwd() when it's looked up. @@ -331,16 +571,25 @@ fs_user_mount_t *filesystem_for_path(const char *path_in, const char **path_unde return fs_mount; } -bool filesystem_native_fatfs(fs_user_mount_t *fs_mount) { - return fs_mount->base.type == &mp_fat_vfs_type && (fs_mount->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) != 0; +static bool filesystem_native_fatfs(supervisor_vfs_t *fs_mount) { + return fs_mount->common.base.type == &mp_fat_vfs_type && + (fs_mount->common.blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) != 0; } -bool filesystem_lock(fs_user_mount_t *fs_mount) { - if (fs_mount->lock_count == 0 && !blockdev_lock(fs_mount)) { +bool filesystem_lock(supervisor_vfs_t *fs_mount) { + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (fs_mount->common.base.type != &mp_fat_vfs_type) { + // littlefs mounts are never exposed through USB MSC, so there is no + // STA_PROTECT to bypass and no lock_count to track. The blockdev lock + // alone excludes other writers. + return blockdev_lock(fs_mount); + } + #endif + if (fs_mount->fat.lock_count == 0 && !blockdev_lock(fs_mount)) { return false; } - fs_mount->lock_count += 1; - // CIRCUITPY-CHANGE: while a non-USB-MSC writer (BLE file transfer, web + fs_mount->fat.lock_count += 1; + // While a non-USB-MSC writer (BLE file transfer, web // workflow, storage.remount) holds the filesystem lock, allow the // FatFS f_open(FA_WRITE) path to bypass STA_PROTECT. Without this, the // disk_ioctl(IOCTL_STATUS) -> filesystem_is_writable_by_python() check @@ -348,27 +597,577 @@ bool filesystem_lock(fs_user_mount_t *fs_mount) { // so even after the lock is held, f_open returns FR_WRITE_PROTECTED. // USB MSC takes the lock via blockdev_lock() directly, NOT via // filesystem_lock(), so this flag is never set on its behalf. - fs_mount->blockdev.flags |= MP_BLOCKDEV_FLAG_IGNORE_WRITE_PROTECTION; + fs_mount->common.blockdev.flags |= MP_BLOCKDEV_FLAG_IGNORE_WRITE_PROTECTION; return true; } -void filesystem_unlock(fs_user_mount_t *fs_mount) { - fs_mount->lock_count -= 1; - if (fs_mount->lock_count == 0) { - // CIRCUITPY-CHANGE: clear the bypass when releasing the lock. - fs_mount->blockdev.flags &= ~MP_BLOCKDEV_FLAG_IGNORE_WRITE_PROTECTION; +void filesystem_unlock(supervisor_vfs_t *fs_mount) { + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (fs_mount->common.base.type != &mp_fat_vfs_type) { + blockdev_unlock(fs_mount); + return; + } + #endif + fs_mount->fat.lock_count -= 1; + if (fs_mount->fat.lock_count == 0) { + // Clear the bypass when releasing the lock. + fs_mount->common.blockdev.flags &= ~MP_BLOCKDEV_FLAG_IGNORE_WRITE_PROTECTION; blockdev_unlock(fs_mount); } } -bool blockdev_lock(fs_user_mount_t *fs_mount) { - if ((fs_mount->blockdev.flags & MP_BLOCKDEV_FLAG_LOCKED) != 0) { +bool blockdev_lock(supervisor_vfs_t *fs_mount) { + if ((fs_mount->common.blockdev.flags & MP_BLOCKDEV_FLAG_LOCKED) != 0) { return false; } - fs_mount->blockdev.flags |= MP_BLOCKDEV_FLAG_LOCKED; + fs_mount->common.blockdev.flags |= MP_BLOCKDEV_FLAG_LOCKED; return true; } -void blockdev_unlock(fs_user_mount_t *fs_mount) { - fs_mount->blockdev.flags &= ~MP_BLOCKDEV_FLAG_LOCKED; +void blockdev_unlock(supervisor_vfs_t *fs_mount) { + fs_mount->common.blockdev.flags &= ~MP_BLOCKDEV_FLAG_LOCKED; +} + +// Supervisor-level file, directory and filesystem helpers shared between FAT +// and littlefs mounts. All of them take or return supervisor_fs_err_t; paths +// are relative to the given mount. See supervisor/filesystem.h for details. + +bool supervisor_vfs_supported(supervisor_vfs_t *vfs) { + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (is_lfs(vfs)) { + return true; + } + #endif + return filesystem_native_fatfs(vfs); +} + +supervisor_fs_err_t supervisor_vfs_open_file(supervisor_vfs_t *vfs, const char *path, uint32_t flags, + uint64_t mtime_ns, supervisor_vfs_file_t *file) { + if (vfs == NULL) { + return SUPERVISOR_FS_NO_FILE; + } + // Only open needs clearing up front; every other field is set on a + // successful open and callers must check the result before using file. + file->open = false; + file->fattime = 0; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + file->vfs = vfs; + if (is_lfs(vfs)) { + struct lfs2_file_config *cfg = &file->file.lfs.cfg; + memset(cfg, 0, sizeof(*cfg)); + cfg->buffer = file->file.lfs.buffer; + // Modification time attribute, committed when a modified file is + // closed. littlefs writes file attributes at close. + if (mtime_ns == 0) { + // No timestamp was requested. Use the same RTC-based time that FAT + // stamps files with via get_fattime(). + mtime_ns = get_fattime_ns(); + } + lfs_mtime_store(file->file.lfs.mtime, mtime_ns); + struct lfs2_attr *attr = &file->file.lfs.attr; + attr->type = LFS_ATTR_MTIME; + attr->buffer = file->file.lfs.mtime; + attr->size = sizeof(file->file.lfs.mtime); + cfg->attrs = attr; + cfg->attr_count = 1; + int lfs_flags; + if ((flags & SUPERVISOR_FS_OPEN_WRITE) != 0) { + lfs_flags = LFS2_O_WRONLY; + } else { + lfs_flags = LFS2_O_RDONLY; + } + if ((flags & SUPERVISOR_FS_OPEN_CREATE) != 0) { + lfs_flags |= LFS2_O_CREAT; + } + if ((flags & SUPERVISOR_FS_OPEN_TRUNCATE) != 0) { + lfs_flags |= LFS2_O_TRUNC; + } + int err = lfs2_file_opencfg(&vfs->lfs2.lfs, &file->file.lfs.lfs2, path, lfs_flags, cfg); + if (err < 0) { + return lfs_error(err); + } + file->open = true; + file->lfs = true; + return SUPERVISOR_FS_OK; + } + #endif + // On FAT-only builds there are no littlefs mounts, and + // every caller has already vetted the mount through + // supervisor_vfs_supported(), so skip the extra checks to keep these + // wrappers as small as plain FatFS calls. + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (!filesystem_native_fatfs(vfs)) { + return SUPERVISOR_FS_IO; + } + #endif + DWORD fattime = mtime_ns != 0 ? fattime_from_ns(mtime_ns) : 0; + if (fattime != 0) { + // FatFS stamps files when they're closed, so remember the timestamp + // and apply it again in supervisor_vfs_close_file(). + override_fattime(fattime); + } + BYTE fa = 0; + if ((flags & SUPERVISOR_FS_OPEN_READ) != 0) { + fa |= FA_READ; + } + if ((flags & SUPERVISOR_FS_OPEN_WRITE) != 0) { + fa |= FA_WRITE; + } + if ((flags & SUPERVISOR_FS_OPEN_TRUNCATE) != 0) { + fa |= FA_CREATE_ALWAYS; + } else if ((flags & SUPERVISOR_FS_OPEN_CREATE) != 0) { + fa |= FA_OPEN_ALWAYS; + } + FRESULT res = f_open(&vfs->fat.fatfs, &file->file.fat, path, fa); + if (fattime != 0) { + override_fattime(0); + } + if (res != FR_OK) { + return fat_error(res); + } + file->open = true; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + file->lfs = false; + #endif + file->fattime = fattime; + return SUPERVISOR_FS_OK; +} + +supervisor_fs_err_t supervisor_vfs_close_file(supervisor_vfs_file_t *file) { + if (!file->open) { + return SUPERVISOR_FS_OK; + } + file->open = false; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (file->lfs) { + // The mtime attribute filled in at open is written out here for a + // modified file. + int err = lfs2_file_close(&file->vfs->lfs2.lfs, &file->file.lfs.lfs2); + return err < 0 ? lfs_error(err) : SUPERVISOR_FS_OK; + } + #endif + if (file->fattime != 0) { + override_fattime(file->fattime); + } + FRESULT res = f_close(&file->file.fat); + if (file->fattime != 0) { + override_fattime(0); + } + return fat_error(res); +} + +supervisor_fs_err_t supervisor_vfs_read_file(supervisor_vfs_file_t *file, void *buf, size_t len, size_t *bytes_read) { + *bytes_read = 0; + if (!file->open) { + return SUPERVISOR_FS_IO; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (file->lfs) { + lfs2_ssize_t read = lfs2_file_read(&file->vfs->lfs2.lfs, &file->file.lfs.lfs2, buf, len); + if (read < 0) { + return lfs_error(read); + } + *bytes_read = read; + return SUPERVISOR_FS_OK; + } + #endif + UINT read; + FRESULT res = f_read(&file->file.fat, buf, len, &read); + *bytes_read = read; + return fat_error(res); +} + +supervisor_fs_err_t supervisor_vfs_write_file(supervisor_vfs_file_t *file, const void *buf, size_t len, size_t *bytes_written) { + *bytes_written = 0; + if (!file->open) { + return SUPERVISOR_FS_IO; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (file->lfs) { + lfs2_ssize_t written = lfs2_file_write(&file->vfs->lfs2.lfs, &file->file.lfs.lfs2, buf, len); + if (written < 0) { + return lfs_error(written); + } + *bytes_written = written; + return SUPERVISOR_FS_OK; + } + #endif + UINT written; + FRESULT res = f_write(&file->file.fat, buf, len, &written); + *bytes_written = written; + return fat_error(res); +} + +supervisor_fs_err_t supervisor_vfs_seek_file(supervisor_vfs_file_t *file, size_t offset) { + if (!file->open) { + return SUPERVISOR_FS_IO; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (file->lfs) { + lfs2_soff_t result = lfs2_file_seek(&file->vfs->lfs2.lfs, &file->file.lfs.lfs2, offset, LFS2_SEEK_SET); + return result < 0 ? lfs_error(result) : SUPERVISOR_FS_OK; + } + #endif + return fat_error(f_lseek(&file->file.fat, offset)); +} + +size_t supervisor_vfs_tell_file(supervisor_vfs_file_t *file) { + if (!file->open) { + return 0; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (file->lfs) { + return lfs2_file_tell(&file->vfs->lfs2.lfs, &file->file.lfs.lfs2); + } + #endif + return f_tell(&file->file.fat); +} + +size_t supervisor_vfs_file_size(supervisor_vfs_file_t *file) { + if (!file->open) { + return 0; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (file->lfs) { + return lfs2_file_size(&file->vfs->lfs2.lfs, &file->file.lfs.lfs2); + } + #endif + return f_size(&file->file.fat); +} + +supervisor_fs_err_t supervisor_vfs_truncate_file(supervisor_vfs_file_t *file) { + if (!file->open) { + return SUPERVISOR_FS_IO; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (file->lfs) { + int err = lfs2_file_truncate(&file->vfs->lfs2.lfs, &file->file.lfs.lfs2, + lfs2_file_tell(&file->vfs->lfs2.lfs, &file->file.lfs.lfs2)); + return err < 0 ? lfs_error(err) : SUPERVISOR_FS_OK; + } + #endif + return fat_error(f_truncate(&file->file.fat)); +} + +supervisor_fs_err_t supervisor_vfs_stat(supervisor_vfs_t *vfs, const char *path, bool *is_dir, size_t *size, uint64_t *mtime_ns) { + if (is_dir != NULL) { + *is_dir = false; + } + if (size != NULL) { + *size = 0; + } + if (mtime_ns != NULL) { + *mtime_ns = 0; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (is_lfs(vfs)) { + struct lfs2_info info; + int err = lfs2_stat(&vfs->lfs2.lfs, path, &info); + if (err < 0) { + return lfs_error(err); + } + bool directory = info.type == LFS2_TYPE_DIR; + if (is_dir != NULL) { + *is_dir = directory; + } + if (size != NULL && !directory) { + *size = info.size; + } + if (mtime_ns != NULL) { + *mtime_ns = lfs_get_path_mtime(&vfs->lfs2.lfs, path); + } + return SUPERVISOR_FS_OK; + } + #endif + FILINFO file_info; + FRESULT res = f_stat(&vfs->fat.fatfs, path, &file_info); + if (res != FR_OK) { + return fat_error(res); + } + bool directory = (file_info.fattrib & AM_DIR) != 0; + if (is_dir != NULL) { + *is_dir = directory; + } + if (size != NULL && !directory) { + *size = file_info.fsize; + } + if (mtime_ns != NULL) { + *mtime_ns = (uint64_t)timeutils_mktime_1970(1980 + (file_info.fdate >> 9), + (file_info.fdate >> 5) & 0xf, + file_info.fdate & 0x1f, + file_info.ftime >> 11, + (file_info.ftime >> 5) & 0x3f, + (file_info.ftime & 0x1f) * 2) * 1000000000ULL; + } + return SUPERVISOR_FS_OK; +} + +supervisor_fs_err_t supervisor_vfs_mkdir(supervisor_vfs_t *vfs, const char *path, uint64_t mtime_ns) { + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (is_lfs(vfs)) { + int err = lfs2_mkdir(&vfs->lfs2.lfs, path); + return err < 0 ? lfs_error(err) : SUPERVISOR_FS_OK; + } + #endif + // See supervisor_vfs_open_file() for why this check is littlefs-only. + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (!filesystem_native_fatfs(vfs)) { + return SUPERVISOR_FS_IO; + } + #endif + DWORD fattime = mtime_ns != 0 ? fattime_from_ns(mtime_ns) : 0; + if (fattime != 0) { + override_fattime(fattime); + } + FRESULT res = f_mkdir(&vfs->fat.fatfs, path); + if (fattime != 0) { + override_fattime(0); + } + return fat_error(res); +} + +supervisor_fs_err_t supervisor_vfs_rename(supervisor_vfs_t *vfs, const char *old_path, const char *new_path) { + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (is_lfs(vfs)) { + int err = lfs2_rename(&vfs->lfs2.lfs, old_path, new_path); + return err < 0 ? lfs_error(err) : SUPERVISOR_FS_OK; + } + #endif + // See supervisor_vfs_open_file() for why this check is littlefs-only. + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (!filesystem_native_fatfs(vfs)) { + return SUPERVISOR_FS_IO; + } + #endif + return fat_error(f_rename(&vfs->fat.fatfs, old_path, new_path)); +} + +supervisor_fs_err_t supervisor_vfs_unlink(supervisor_vfs_t *vfs, const char *path) { + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (is_lfs(vfs)) { + int err = lfs2_remove(&vfs->lfs2.lfs, path); + return err < 0 ? lfs_error(err) : SUPERVISOR_FS_OK; + } + #endif + // See supervisor_vfs_open_file() for why this check is littlefs-only. + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (!filesystem_native_fatfs(vfs)) { + return SUPERVISOR_FS_IO; + } + #endif + return fat_error(f_unlink(&vfs->fat.fatfs, path)); +} + +supervisor_fs_err_t supervisor_vfs_opendir(supervisor_vfs_t *vfs, const char *path, supervisor_vfs_dir_t *dir) { + // Only open needs clearing up front; the rest of dir is initialized by + // f_opendir/lfs2_dir_open on success and callers must check the result + // before using dir. + dir->open = false; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + dir->vfs = vfs; + if (is_lfs(vfs)) { + int err = lfs2_dir_open(&vfs->lfs2.lfs, &dir->dir.lfs2, path); + if (err < 0) { + return lfs_error(err); + } + dir->open = true; + dir->lfs = true; + // Store the path (without a trailing slash) for per-entry mtime + // lookups. If it doesn't fit, mtimes will simply read as 0. + size_t len = strlen(path); + while (len > 0 && path[len - 1] == '/') { + len--; + } + if (len > sizeof(dir->path) - 1) { + len = sizeof(dir->path) - 1; + } + memcpy(dir->path, path, len); + dir->path[len] = '\0'; + return SUPERVISOR_FS_OK; + } + #endif + // See supervisor_vfs_open_file() for why this check is littlefs-only. + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (!filesystem_native_fatfs(vfs)) { + return SUPERVISOR_FS_IO; + } + #endif + FRESULT res = f_opendir(&vfs->fat.fatfs, &dir->dir.fat, path); + if (res != FR_OK) { + return fat_error(res); + } + dir->open = true; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + dir->lfs = false; + #endif + return SUPERVISOR_FS_OK; +} + +supervisor_fs_err_t supervisor_vfs_readdir(supervisor_vfs_dir_t *dir, char *name, size_t name_len, bool *is_dir, size_t *size, uint64_t *mtime_ns) { + if (is_dir != NULL) { + *is_dir = false; + } + if (size != NULL) { + *size = 0; + } + if (mtime_ns != NULL) { + *mtime_ns = 0; + } + if (!dir->open) { + return SUPERVISOR_FS_IO; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (dir->lfs) { + struct lfs2_info info; + int res; + do { + res = lfs2_dir_read(&dir->vfs->lfs2.lfs, &dir->dir.lfs2, &info); + if (res < 0) { + return lfs_error(res); + } + if (res == 0) { + // End of the directory. + name[0] = '\0'; + return SUPERVISOR_FS_OK; + } + // littlefs lists the "." and ".." entries; FatFS doesn't, so skip + // them for parity. + } while (info.name[0] == '.' && + (info.name[1] == '\0' || (info.name[1] == '.' && info.name[2] == '\0'))); + size_t name_chars = strlen(info.name); + if (name_chars > name_len - 1) { + name_chars = name_len - 1; + } + memcpy(name, info.name, name_chars); + name[name_chars] = '\0'; + bool directory = info.type == LFS2_TYPE_DIR; + if (is_dir != NULL) { + *is_dir = directory; + } + if (size != NULL && !directory) { + *size = info.size; + } + if (mtime_ns != NULL && !directory) { + // littlefs keeps modification times as per-path attributes, so + // build the entry's full path. The stored directory path is + // temporarily extended to hold the entry name. + size_t len = strlen(dir->path); + if (len + 1 + name_chars + 1 <= sizeof(dir->path)) { + dir->path[len] = '/'; + memcpy(dir->path + len + 1, info.name, name_chars); + dir->path[len + 1 + name_chars] = '\0'; + *mtime_ns = lfs_get_path_mtime(&dir->vfs->lfs2.lfs, dir->path); + dir->path[len] = '\0'; + } + } + return SUPERVISOR_FS_OK; + } + #endif + FILINFO file_info; + FRESULT res = f_readdir(&dir->dir.fat, &file_info); + if (res != FR_OK) { + return fat_error(res); + } + if (file_info.fname[0] == '\0') { + // End of the directory. + name[0] = '\0'; + return SUPERVISOR_FS_OK; + } + size_t name_chars = strlen(file_info.fname); + if (name_chars > name_len - 1) { + name_chars = name_len - 1; + } + memcpy(name, file_info.fname, name_chars); + name[name_chars] = '\0'; + bool directory = (file_info.fattrib & AM_DIR) != 0; + if (is_dir != NULL) { + *is_dir = directory; + } + if (size != NULL && !directory) { + *size = file_info.fsize; + } + if (mtime_ns != NULL) { + *mtime_ns = (uint64_t)timeutils_mktime_1970(1980 + (file_info.fdate >> 9), + (file_info.fdate >> 5) & 0xf, + file_info.fdate & 0x1f, + file_info.ftime >> 11, + (file_info.ftime >> 5) & 0x3f, + (file_info.ftime & 0x1f) * 2) * 1000000000ULL; + } + return SUPERVISOR_FS_OK; +} + +supervisor_fs_err_t supervisor_vfs_rewinddir(supervisor_vfs_dir_t *dir) { + if (!dir->open) { + return SUPERVISOR_FS_IO; + } + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (dir->lfs) { + int err = lfs2_dir_rewind(&dir->vfs->lfs2.lfs, &dir->dir.lfs2); + return err < 0 ? lfs_error(err) : SUPERVISOR_FS_OK; + } + #endif + f_readdir(&dir->dir.fat, NULL); + return SUPERVISOR_FS_OK; +} + +supervisor_fs_err_t supervisor_vfs_closedir(supervisor_vfs_dir_t *dir) { + if (!dir->open) { + return SUPERVISOR_FS_OK; + } + dir->open = false; + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (dir->lfs) { + int err = lfs2_dir_close(&dir->vfs->lfs2.lfs, &dir->dir.lfs2); + return err < 0 ? lfs_error(err) : SUPERVISOR_FS_OK; + } + #endif + return fat_error(f_closedir(&dir->dir.fat)); +} + +supervisor_fs_err_t supervisor_vfs_statfs(supervisor_vfs_t *vfs, size_t *block_size, size_t *total_blocks, size_t *free_blocks) { + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (is_lfs(vfs)) { + struct lfs2_config *config = &vfs->lfs2.config; + if (block_size != NULL) { + *block_size = config->block_size; + } + if (total_blocks != NULL) { + *total_blocks = config->block_count; + } + if (free_blocks != NULL) { + lfs2_ssize_t used = lfs2_fs_size(&vfs->lfs2.lfs); + *free_blocks = used < 0 ? 0 : config->block_count - used; + } + return SUPERVISOR_FS_OK; + } + #endif + // See supervisor_vfs_open_file() for why this check is littlefs-only. + #if CIRCUITPY_FILESYSTEM_LITTLEFS + if (!filesystem_native_fatfs(vfs)) { + return SUPERVISOR_FS_IO; + } + #endif + FATFS *fatfs = &vfs->fat.fatfs; + DWORD free_clusters = 0; + FRESULT res = f_getfree(fatfs, &free_clusters); + if (res != FR_OK) { + return fat_error(res); + } + size_t ssize; + #if FF_MAX_SS != FF_MIN_SS + ssize = fatfs->ssize; + #else + ssize = FF_MIN_SS; + #endif + if (block_size != NULL) { + *block_size = fatfs->csize * ssize; + } + if (total_blocks != NULL) { + *total_blocks = fatfs->n_fatent - 2; + } + if (free_blocks != NULL) { + *free_blocks = free_clusters; + } + return SUPERVISOR_FS_OK; } diff --git a/supervisor/shared/flash.c b/supervisor/shared/flash.c index ccb60efe321..a75adc4091f 100644 --- a/supervisor/shared/flash.c +++ b/supervisor/shared/flash.c @@ -150,6 +150,13 @@ static mp_uint_t flash_read_blocks(mp_obj_t self_in, uint8_t *dest, uint32_t blo } static volatile bool filesystem_dirty = false; +void supervisor_flash_mark_dirty(void) { + if (!filesystem_dirty) { + // Turn on ticks so that we can flush after a period of time elapses. + supervisor_enable_tick(); + filesystem_dirty = true; + } +} static mp_uint_t flash_write_blocks(mp_obj_t self_in, const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { if (block_num == 0) { @@ -159,11 +166,7 @@ static mp_uint_t flash_write_blocks(mp_obj_t self_in, const uint8_t *src, uint32 // can't write MBR, but pretend we did return 0; } else { - if (!filesystem_dirty) { - // Turn on ticks so that we can flush after a period of time elapses. - supervisor_enable_tick(); - filesystem_dirty = true; - } + supervisor_flash_mark_dirty(); block_num -= PART1_START_BLOCK; #if CIRCUITPY_SAVES_PARTITION_SIZE > 0 mp_vfs_blockdev_t *self = (mp_vfs_blockdev_t *)self_in; diff --git a/supervisor/shared/port.c b/supervisor/shared/port.c index 15b0751c757..757c03b2d39 100644 --- a/supervisor/shared/port.c +++ b/supervisor/shared/port.c @@ -105,11 +105,12 @@ MP_WEAK bool port_boot_button_pressed(void) { // Init/deinit the boot button every time in case it is used for LEDs. digitalio_digitalinout_obj_t boot_button; common_hal_digitalio_digitalinout_construct(&boot_button, CIRCUITPY_BOOT_BUTTON); - common_hal_digitalio_digitalinout_switch_to_input(&boot_button, PULL_UP); + common_hal_digitalio_digitalinout_switch_to_input(&boot_button, + CIRCUITPY_BOOT_BUTTON_ACTIVE_HIGH ? PULL_DOWN : PULL_UP); common_hal_time_delay_ms(1); - bool button_pressed = !common_hal_digitalio_digitalinout_get_value(&boot_button); + bool value = common_hal_digitalio_digitalinout_get_value(&boot_button); common_hal_digitalio_digitalinout_deinit(&boot_button); - return button_pressed; + return value == CIRCUITPY_BOOT_BUTTON_ACTIVE_HIGH; #else return false; #endif @@ -119,6 +120,10 @@ MP_WEAK bool port_boot_button_pressed(void) { MP_WEAK void port_gc_collect(void) { } +// Ports may provide an implementation of this function if it is needed +MP_WEAK void port_gc_deinit(void) { +} + // Allocates an object in the port heap, not the VM heap, and also sets type, for mp_obj_malloc{,_var} macros. MP_NOINLINE void *mp_obj_port_malloc_helper(size_t num_bytes, const mp_obj_type_t *type) { mp_obj_base_t *base = (mp_obj_base_t *)port_malloc_zero(num_bytes, false); diff --git a/supervisor/shared/serial.c b/supervisor/shared/serial.c index 1bfb8d73b3c..392ac08506b 100644 --- a/supervisor/shared/serial.c +++ b/supervisor/shared/serial.c @@ -191,8 +191,10 @@ void serial_early_init(void) { common_hal_busio_uart_construct(&console_uart, console_tx, console_rx, NULL, NULL, NULL, false, CIRCUITPY_CONSOLE_UART_BAUDRATE, 8, BUSIO_UART_PARITY_NONE, 1, 1.0f, sizeof(console_uart_rx_buf), console_uart_rx_buf, true); + #if CIRCUITPY_BULK_RESET common_hal_busio_uart_never_reset(&console_uart); #endif + #endif board_serial_early_init(); diff --git a/supervisor/shared/settings.c b/supervisor/shared/settings.c index 1414195411e..fe13a4e6416 100644 --- a/supervisor/shared/settings.c +++ b/supervisor/shared/settings.c @@ -26,9 +26,9 @@ #include "extmod/vfs_fat.h" #if CIRCUITPY_SETTINGS_TOML +#if defined(UNIX) typedef FIL file_arg; static bool open_file(const char *name, file_arg *file_handle) { - #if defined(UNIX) nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_obj_t file_obj = mp_call_function_2( @@ -41,15 +41,6 @@ static bool open_file(const char *name, file_arg *file_handle) { } else { return false; } - #else - fs_user_mount_t *fs_mount = filesystem_circuitpy(); - if (fs_mount == NULL) { - return false; - } - FATFS *fatfs = &fs_mount->fatfs; - FRESULT result = f_open(fatfs, file_handle, name, FA_READ); - return result == FR_OK; - #endif } static void close_file(file_arg *file_handle) { @@ -72,6 +63,46 @@ static uint8_t get_next_byte(FIL *file_handle) { static void seek_eof(file_arg *file_handle) { f_lseek(file_handle, f_size(file_handle)); } +#else +// Outside of the unix port, settings.toml is read through the supervisor +// filesystem API so it works on both FAT and littlefs mounts. +typedef supervisor_vfs_file_t file_arg; + +static bool open_file(const char *name, file_arg *file_handle) { + supervisor_vfs_t *fs_mount = filesystem_circuitpy(); + if (fs_mount == NULL) { + return false; + } + return supervisor_vfs_open_file(fs_mount, name, SUPERVISOR_FS_OPEN_READ, 0, file_handle) == SUPERVISOR_FS_OK; +} + +static void close_file(file_arg *file_handle) { + supervisor_vfs_close_file(file_handle); +} + +// Reads mark the end of the file by closing the handle. +static bool is_eof(file_arg *file_handle) { + return !file_handle->open; +} + +// Return 0 if there is no next character (EOF). +static uint8_t get_next_byte(file_arg *file_handle) { + uint8_t character = 0; + size_t quantity_read; + // If there's an error or quantity_read is 0, character will remain 0. + supervisor_fs_err_t err = supervisor_vfs_read_file(file_handle, &character, 1, &quantity_read); + // If we hit the end of the file (or an error), close the handle so that + // is_eof() reports the file is done. + if (err != SUPERVISOR_FS_OK || quantity_read == 0) { + close_file(file_handle); + } + return character; +} + +static void seek_eof(file_arg *file_handle) { + supervisor_vfs_seek_file(file_handle, supervisor_vfs_file_size(file_handle)); +} +#endif // For a fixed buffer, record the required size rather than throwing static void vstr_add_byte_nonstd(vstr_t *vstr, byte b) { diff --git a/supervisor/shared/status_leds.c b/supervisor/shared/status_leds.c index ca29fd79212..51eb95e0e68 100644 --- a/supervisor/shared/status_leds.c +++ b/supervisor/shared/status_leds.c @@ -192,7 +192,7 @@ void status_led_deinit(void) { // it up and stop the reset period. while (port_get_raw_ticks(NULL) < next_start_raw_ticks) { } - common_hal_reset_pin(MICROPY_HW_NEOPIXEL); + common_hal_digitalio_digitalinout_deinit(&status_neopixel); #elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK) #if CIRCUITPY_BITBANG_APA102 @@ -319,13 +319,17 @@ void init_rxtx_leds(void) { #if CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_RX) common_hal_digitalio_digitalinout_construct(&rx_led, MICROPY_HW_LED_RX); common_hal_digitalio_digitalinout_switch_to_output(&rx_led, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_digitalio_digitalinout_never_reset(&rx_led); #endif + #endif #if CIRCUITPY_DIGITALIO && defined(MICROPY_HW_LED_TX) common_hal_digitalio_digitalinout_construct(&tx_led, MICROPY_HW_LED_TX); common_hal_digitalio_digitalinout_switch_to_output(&tx_led, true, DRIVE_MODE_PUSH_PULL); + #if CIRCUITPY_BULK_RESET common_hal_digitalio_digitalinout_never_reset(&tx_led); #endif + #endif } void deinit_rxtx_leds(void) { diff --git a/supervisor/shared/translate/compressed_string.h b/supervisor/shared/translate/compressed_string.h index 89dd75c22ae..7bfe2000b73 100644 --- a/supervisor/shared/translate/compressed_string.h +++ b/supervisor/shared/translate/compressed_string.h @@ -13,38 +13,41 @@ // The format of the compressed data is: // - the size of the uncompressed string in UTF-8 bytes, encoded as a // (compress_max_length_bits)-bit number. compress_max_length_bits is -// computed during dictionary generation time, and happens to be 8 -// for all current platforms. However, it'll probably end up being -// 9 in some translations sometime in the future. This length excludes +// computed during dictionary generation time, and is 8 for all +// current translations except ru, which needs 9. This length excludes // the trailing NUL, though notably decompress_length includes it. // -// - followed by the huffman encoding of the individual code -// points that make up the string. The trailing "\0" is not -// represented by a huffman code, but is implied by the length. -// (building the huffman encoding on UTF-16 code points gave better -// compression than building it on UTF-8 bytes) +// - followed by the huffman encoding of the symbols that make up the +// string. The trailing "\0" is not represented by a huffman code, but +// is implied by the length. // -// - If possible, the code points are represented as uint8_t values, with -// 0..127 representing themselves and 160..255 representing another range -// of Unicode, controlled by translation_offset and translation_offstart. -// If this is not possible, uint16_t values are used. At present, no translation -// requires code points not in the BMP, so this is adequate. +// - Each symbol is coded with one of TRANSLATION_CLASSES canonical Huffman +// tables, chosen by the class of the previously decoded byte: start of +// string or space, lowercase, uppercase, digit, '%', other, or >= 0x80. +// base_class() computes that seven-way class and class_map[] collapses it +// to a table index; the generator picks how many tables pay for themselves. +// The per-table code-length counts are rows of LENGTHS_ROW bytes in +// lengths[], and the symbols of table n are values[values_offset[n]..]. // -// - code points starting at 128 (word_start) and potentially extending -// to 255 (word_end) (but never interfering with the target -// language's used code points) stand for dictionary entries in a -// dictionary with size up to 256 code points. The dictionary entries -// are computed with a heuristic based on frequent substrings of 2 to -// 9 code points. These are called "words" but are not, grammatically -// speaking, words. They're just spans of code points that frequently -// occur together. They are ordered shortest to longest. +// - Symbol values 0..0x7F are ASCII (with 1, 2 and 3 reserved for the +// escapes below). Values from 0x80 up to 0x80 + alphabet_size - 1 are the +// most frequent non-ASCII characters used by the translation, renumbered +// by decreasing frequency; alphabet[] maps them back to Unicode code +// points. Most translations fit their whole non-ASCII repertoire in the +// alphabet. Those that don't (ja, ko) keep the 32 to 80 most frequent +// characters there and escape the rest: value 2 is followed by a +// rare_index_bits-bit index into rare_chars[] (characters used more than +// once), value 3 by a raw 16-bit code point (characters used once, which +// are cheaper without a table entry). All symbols are therefore 8 bits. +// At present, no translation requires code points outside the BMP, so +// this is adequate. // -// - If the translation uses a lot of code points or widely spaced code points, -// then the huffman table entries are UTF-16 code points. But if the translation -// uses only ASCII 7-bit code points plus a SMALL range of higher code points that -// still fit in 8 bits, translation_offset and translation_offstart are used to -// renumber the code points so that they still fit within 8 bits. (it's very beneficial -// for mchar_t to be 8 bits instead of 16!) +// - Symbol values from word_start to word_end stand for dictionary entries +// in a dictionary of up to 256 - word_start entries. The dictionary +// entries are computed with a heuristic based on frequent substrings of 2 +// to 11 symbols. These are called "words" but are not, grammatically +// speaking, words. They're just spans of symbols that frequently occur +// together. They are ordered shortest to longest. // // - dictionary entries are non-overlapping, and the _ending_ index of each // entry is stored in an array. A count of words of each length, from @@ -56,10 +59,11 @@ // - Value 1 ('\1') is used to indicate that a QSTR number follows. the // QSTR is encoded as a fixed number of bits (translation_qstr_bits), e.g., // 10 bits if the highest core qstr is from 512 to 1023 inclusive. -// (maketranslationdata uses a simple heuristic where any qstr >= 3 -// characters long is encoded in this way; this is simple but probably not -// optimal. In fact, the rule of >= 2 characters is better for SOME languages -// on SOME boards.) +// (maketranslationdata uses a simple heuristic where any qstr >= 4 +// characters long may be encoded in this way; whether a given occurrence +// is coded as a qstr or as characters is decided per occurrence by the +// parser, which picks the tokenization of each string that takes the +// fewest bits.) // // The "data" / "tail" construct is so that the struct's last member is a // "flexible array". However, the _only_ member is not permitted to be @@ -76,6 +80,26 @@ typedef struct compressed_string { const uint8_t tail[]; } const *mp_rom_error_text_t; +// Class of the previously decoded byte, which selects the Huffman table for +// the next symbol. Must match base_class() in py/maketranslationdata.py. +typedef enum { + CLASS_START_OR_SPACE = 0, + CLASS_LOWER = 1, + CLASS_UPPER = 2, + CLASS_DIGIT = 3, + CLASS_OTHER = 4, + CLASS_PERCENT = 5, + CLASS_NON_ASCII = 6, +} translation_class_t; + +// Symbol values with a special meaning; every other value is a character or a +// dictionary word. Must match py/maketranslationdata.py. +typedef enum { + SYMBOL_QSTR = 1, // followed by translation_qstr_bits bits of qstr index + SYMBOL_RARE_INDEX = 2, // followed by rare_index_bits bits of rare_chars[] index + SYMBOL_RARE_RAW = 3, // followed by a 16-bit code point +} translation_symbol_t; + // Return the compressed, translated version of a source string // Usually, due to LTO, this is optimized into a load of a constant // pointer. diff --git a/supervisor/shared/translate/translate.c b/supervisor/shared/translate/translate.c index b590b385a71..d0c05dda6e5 100644 --- a/supervisor/shared/translate/translate.c +++ b/supervisor/shared/translate/translate.c @@ -23,7 +23,7 @@ void serial_write_compressed(mp_rom_error_text_t compressed) { mp_printf(MP_PYTHON_PRINTER, "%S", compressed); } -static void get_word(int n, const mchar_t **pos, const mchar_t **end) { +static void get_word(int n, const uint8_t **pos, const uint8_t **end) { int len = minlen; int i = 0; *pos = words; @@ -37,14 +37,10 @@ static void get_word(int n, const mchar_t **pos, const mchar_t **end) { *end = *pos + len; } -static void put_utf8(vstr_t *vstr, int u) { - if (u >= translation_offstart) { - u += translation_offset; - } +static void put_utf8(vstr_t *vstr, unsigned u) { if (word_start <= u && u <= word_end) { - uint n = (u - word_start); - const mchar_t *pos, *end; - get_word(n, &pos, &end); + const uint8_t *pos, *end; + get_word(u - word_start, &pos, &end); // note that at present, entries in the words table are // guaranteed not to represent words themselves, so this adds // at most 1 level of recursive call @@ -53,9 +49,39 @@ static void put_utf8(vstr_t *vstr, int u) { } return; } + // alphabet_size is a compile-time constant; the test folds away when it is 0. + if (alphabet_size > 0 && u >= 0x80 && u < 0x80 + alphabet_size) { + u = alphabet[u - 0x80]; + } vstr_add_char(vstr, u); } +// The Huffman table used for the next symbol depends on the class of the last +// byte written. Classifying the last byte is the same as classifying the last +// code point: every byte of a multi-byte UTF-8 sequence is >= 0x80. +// This must match base_class() in py/maketranslationdata.py. +static translation_class_t base_class(uint8_t last) { + if (last == ' ') { + return CLASS_START_OR_SPACE; + } + if (last >= 'a' && last <= 'z') { + return CLASS_LOWER; + } + if (last >= 'A' && last <= 'Z') { + return CLASS_UPPER; + } + if (last >= '0' && last <= '9') { + return CLASS_DIGIT; + } + if (last == '%') { + return CLASS_PERCENT; + } + if (last >= 0x80) { + return CLASS_NON_ASCII; + } + return CLASS_OTHER; +} + uint16_t decompress_length(mp_rom_error_text_t compressed) { #ifndef NO_QSTR #if (compress_max_length_bits <= 8) @@ -99,23 +125,34 @@ static void decompress_vstr(mp_rom_error_text_t compressed, vstr_t *decompressed size_t alloc = decompressed->alloc - 1; // Stop one early because the last byte is always NULL. for (; decompressed->len < alloc;) { + // The start of a string is classed like a space. + uint8_t last = decompressed->len ? (uint8_t)decompressed->buf[decompressed->len - 1] : ' '; + uint8_t cls = class_map[base_class(last)]; + const uint8_t *len_row = lengths + cls * LENGTHS_ROW; + uint32_t bits = 0; uint8_t bit_length = 0; - uint32_t max_code = lengths[0]; - uint32_t searched_length = lengths[0]; + uint32_t max_code = len_row[0]; + uint32_t searched_length = len_row[0]; while (true) { bits = (bits << 1) | next_bit(&b); bit_length += 1; if (max_code > 0 && bits < max_code) { break; } - max_code = (max_code << 1) + lengths[bit_length]; - searched_length += lengths[bit_length]; + max_code = (max_code << 1) + len_row[bit_length]; + searched_length += len_row[bit_length]; } - int v = values[searched_length + bits - max_code]; - if (v == 1) { + unsigned v = values[values_offset[cls] + searched_length + bits - max_code]; + if (v == SYMBOL_QSTR) { qstr q = get_nbits(&b, translation_qstr_bits); vstr_add_str(decompressed, qstr_str(q)); + } else if (rare_index_count > 0 && v == SYMBOL_RARE_INDEX) { + // A non-ASCII character outside the dense alphabet, by table index. + vstr_add_char(decompressed, rare_chars[get_nbits(&b, rare_index_bits)]); + } else if (rare_raw_count > 0 && v == SYMBOL_RARE_RAW) { + // A non-ASCII character used only once, as a raw code point. + vstr_add_char(decompressed, get_nbits(&b, 16)); } else { put_utf8(decompressed, v); } diff --git a/supervisor/shared/translate/translate_impl.h b/supervisor/shared/translate/translate_impl.h index 5139210db30..816292aa050 100644 --- a/supervisor/shared/translate/translate_impl.h +++ b/supervisor/shared/translate/translate_impl.h @@ -13,8 +13,8 @@ #include "supervisor/shared/translate/compressed_string.h" #ifndef NO_QSTR -#define QDEF0(id, hash, len, str) -#define QDEF1(id, hash, len, str) +#define QDEF0(id, hash, len, offset, str) +#define QDEF1(id, hash, len, offset, str) #define TRANSLATION(english_id, number) extern struct compressed_string translation##number; #include "genhdr/qstrdefs.generated.h" #undef QDEF0 @@ -34,8 +34,8 @@ __attribute__((always_inline)) // optimization. __attribute__((no_instrument_function)) mp_rom_error_text_t MP_COMPRESSED_ROM_TEXT(const char *original) { #ifndef NO_QSTR - #define QDEF0(id, hash, len, str) - #define QDEF1(id, hash, len, str) + #define QDEF0(id, hash, len, offset, str) + #define QDEF1(id, hash, len, offset, str) #define TRANSLATION(english_id, number) if (strcmp(original, english_id) == 0) { return (mp_rom_error_text_t)&translation##number; } else #include "genhdr/qstrdefs.generated.h" #undef TRANSLATION diff --git a/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index ac63542b8e4..8bd920c996f 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -210,7 +210,6 @@ extern "C" { #define CFG_TUH_HUB 2 #define CFG_TUH_CDC 0 #define CFG_TUH_MSC 0 -#define CFG_TUH_VENDOR 0 #define CFG_TUH_API_EDPT_XFER 1 // max device support (excluding hub device) @@ -228,6 +227,7 @@ extern "C" { #define nrf52_errata_187 errata_187 #define nrf52_errata_171 errata_171 #define nrf52_errata_166 errata_166 +#define nrf52_errata_199 errata_199 #ifdef __cplusplus } diff --git a/supervisor/shared/usb/usb_msc_flash.c b/supervisor/shared/usb/usb_msc_flash.c index 5fcd2c8d62b..face2711b31 100644 --- a/supervisor/shared/usb/usb_msc_flash.c +++ b/supervisor/shared/usb/usb_msc_flash.c @@ -137,7 +137,8 @@ size_t usb_msc_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *desc // We hardcode LUN -> mount mapping so that it doesn't changes with saves and // SD card appearing and disappearing. static fs_user_mount_t *get_vfs(int lun) { - fs_user_mount_t *root = filesystem_circuitpy(); + supervisor_vfs_t *root_vfs = filesystem_circuitpy(); + fs_user_mount_t *root = root_vfs == NULL ? NULL : &root_vfs->fat; if (lun == 0) { return root; } @@ -146,7 +147,8 @@ static fs_user_mount_t *get_vfs(int lun) { #ifdef SAVES_LUN if (lun == SAVES_LUN) { const char *path_under_mount; - fs_user_mount_t *saves = filesystem_for_path("/saves", &path_under_mount); + supervisor_vfs_t *saves_mount = filesystem_for_path("/saves", &path_under_mount); + fs_user_mount_t *saves = saves_mount == NULL ? NULL : &saves_mount->fat; if (saves != root && (saves->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) != 0 && !gc_ptr_on_heap(saves)) { return saves; @@ -156,7 +158,8 @@ static fs_user_mount_t *get_vfs(int lun) { #ifdef SDCARD_LUN if (lun == SDCARD_LUN) { const char *path_under_mount; - fs_user_mount_t *sdcard = filesystem_for_path("/sd", &path_under_mount); + supervisor_vfs_t *sdcard_mount = filesystem_for_path("/sd", &path_under_mount); + fs_user_mount_t *sdcard = sdcard_mount == NULL ? NULL : &sdcard_mount->fat; // If sdcard ("/sd") is on the root filesystem, nothing has been mounted there, so don't // return it as a separate filesystem. // If the SD card was automounted at startup, then it persists across VMs and its fs_user_mount_t is @@ -177,8 +180,10 @@ static fs_user_mount_t *get_vfs(int lun) { #ifdef EMMC_LUN if (lun == EMMC_LUN) { const char *path_under_mount; + fs_user_mount_t *emmc; - fs_user_mount_t *emmc = filesystem_for_path(CIRCUITPY_EMMC_MOUNT_PATH, &path_under_mount); + supervisor_vfs_t *emmc_mount = filesystem_for_path(CIRCUITPY_EMMC_MOUNT_PATH, &path_under_mount); + emmc = emmc_mount == NULL ? NULL : &emmc_mount->fat; // Unlike the SD card there is no heap-mount case to allow: the eMMC's // drive exists only when the supervisor mounted it, and // that mount is static. A user mount made by code.py stays a Python @@ -214,7 +219,7 @@ void usb_msc_umount(void) { if (vfs == NULL) { continue; } - blockdev_unlock(vfs); + blockdev_unlock((supervisor_vfs_t *)vfs); locked[i] = false; } } @@ -296,7 +301,7 @@ bool tud_msc_is_writable_cb(uint8_t lun) { return false; } // Lock the blockdev once we say we're writable. - if (!locked[lun] && !blockdev_lock(vfs)) { + if (!locked[lun] && !blockdev_lock((supervisor_vfs_t *)vfs)) { return false; } locked[lun] = true; @@ -465,7 +470,7 @@ bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, boo if (disk_ioctl(current_mount, CTRL_SYNC, NULL) != RES_OK) { return false; } else { - blockdev_unlock(current_mount); + blockdev_unlock((supervisor_vfs_t *)current_mount); ejected[lun] = true; locked[lun] = false; } diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index be352d6c0b3..0d41aea3d4c 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -17,10 +17,9 @@ #include "genhdr/mpversion.h" #include "py/mperrno.h" #include "py/mpstate.h" +#include "py/persistentcode.h" #include "shared-bindings/wifi/Radio.h" -#include "shared/timeutils/timeutils.h" -#include "supervisor/fatfs.h" #include "supervisor/filesystem.h" #include "supervisor/port.h" #include "supervisor/shared/reload.h" @@ -729,11 +728,13 @@ static void _reply_redirect(socketpool_socket_obj_t *socket, _request *request, } #endif -static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *request, fs_user_mount_t *fs_mount, FF_DIR *dir, const char *request_path, const char *path) { - FILINFO file_info; - char *fn = file_info.fname; - FRESULT res = f_readdir(dir, &file_info); - if (res != FR_OK) { +static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *request, supervisor_vfs_t *fs_mount, supervisor_vfs_dir_t *dir, const char *request_path, const char *path) { + char fn[FF_MAX_LFN + 1]; + bool is_dir = false; + size_t file_size = 0; + uint64_t mtime_ns = 0; + supervisor_fs_err_t res = supervisor_vfs_readdir(dir, fn, sizeof(fn), &is_dir, &file_size, &mtime_ns); + if (res != SUPERVISOR_FS_OK) { _reply_missing(socket, request); return; } @@ -743,18 +744,12 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req _send_str(socket, "\r\n"); mp_print_t _socket_print = {socket, _print_chunk}; - // Send mount info. - DWORD free_clusters = 0; - FATFS *fatfs = &fs_mount->fatfs; - f_getfree(fatfs, &free_clusters); - size_t ssize; - #if FF_MAX_SS != FF_MIN_SS - ssize = fatfs->ssize; - #else - ssize = FF_MIN_SS; - #endif - uint32_t cluster_size = fatfs->csize * ssize; - uint32_t total_clusters = fatfs->n_fatent - 2; + // Send mount info. Counts are in filesystem blocks: clusters on FAT, + // blocks on littlefs. + size_t block_size = 0; + size_t total_blocks = 0; + size_t free_blocks = 0; + supervisor_vfs_statfs(fs_mount, &block_size, &total_blocks, &free_blocks); const char *writable = "false"; // Test to see if we can grab the write lock. USB will grab the underlying @@ -768,20 +763,20 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req "{\"free\": %u, " "\"total\": %u, " "\"block_size\": %u, " - "\"writable\": %s, ", free_clusters, total_clusters, cluster_size, writable); + "\"writable\": %s, ", free_blocks, total_blocks, block_size, writable); // Send file list _send_chunk(socket, "\"files\": ["); bool first = true; - while (res == FR_OK && fn[0] != 0) { + while (res == SUPERVISOR_FS_OK && fn[0] != 0) { if (!first) { _send_chunk(socket, ","); } _send_chunks(socket, - "{\"name\": \"", file_info.fname, "\",", + "{\"name\": \"", fn, "\",", "\"directory\": ", NULL); - if ((file_info.fattrib & AM_DIR) != 0) { + if (is_dir) { _send_chunk(socket, "true"); } else { _send_chunk(socket, "false"); @@ -790,31 +785,31 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req // LittleFS. _send_chunk(socket, ", "); - uint32_t truncated_time = timeutils_mktime(1980 + (file_info.fdate >> 9), - (file_info.fdate >> 5) & 0xf, - file_info.fdate & 0x1f, - file_info.ftime >> 11, - (file_info.ftime >> 5) & 0x3f, - (file_info.ftime & 0x1f) * 2); - - // Manually append zeros to make the time nanoseconds. Support for printing 64 bit numbers - // varies across chipsets. - mp_printf(&_socket_print, "\"modified_ns\": %lu000000000, ", truncated_time); - size_t file_size = 0; - if ((file_info.fattrib & AM_DIR) == 0) { - file_size = file_info.fsize; + // mpprint doesn't support 64-bit formats on all targets, so print the + // seconds and zero-padded remainder of the nanosecond value separately. + mp_uint_t mtime_secs = (mp_uint_t)(mtime_ns / 1000000000ULL); + mp_uint_t mtime_frac = (mp_uint_t)(mtime_ns % 1000000000ULL); + if (mtime_secs == 0) { + // Avoid a leading zero, which isn't valid JSON. + mp_printf(&_socket_print, "\"modified_ns\": %u, ", mtime_frac); + } else { + mp_printf(&_socket_print, "\"modified_ns\": %u%09u, ", mtime_secs, mtime_frac); + } + if (!is_dir) { + mp_printf(&_socket_print, "\"file_size\": %d }", file_size); + } else { + _send_chunk(socket, "\"file_size\": 0 }"); } - mp_printf(&_socket_print, "\"file_size\": %d }", file_size); first = false; - res = f_readdir(dir, &file_info); + res = supervisor_vfs_readdir(dir, fn, sizeof(fn), &is_dir, &file_size, &mtime_ns); } _send_chunk(socket, "]}"); _send_chunk(socket, ""); } -static void _reply_with_file(socketpool_socket_obj_t *socket, _request *request, const char *filename, FIL *active_file) { - uint32_t total_length = f_size(active_file); +static void _reply_with_file(socketpool_socket_obj_t *socket, _request *request, const char *filename, supervisor_vfs_file_t *active_file) { + uint32_t total_length = supervisor_vfs_file_size(active_file); _send_str(socket, "HTTP/1.1 200 OK\r\n"); mp_print_t _socket_print = {socket, _print_raw}; @@ -839,7 +834,7 @@ static void _reply_with_file(socketpool_socket_obj_t *socket, _request *request, while (total_read < total_length) { uint8_t data_buffer[64]; size_t quantity_read; - f_read(active_file, data_buffer, 64, &quantity_read); + supervisor_vfs_read_file(active_file, data_buffer, 64, &quantity_read); total_read += quantity_read; // When getting near the end of the file, disable Nagle's combining algorithm so that // data is sent immediately. @@ -933,6 +928,9 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request * "\"build_date\": \"" MICROPY_BUILD_DATE "\", " "\"board_name\": \"%s\", " "\"mcu_name\": \"" MICROPY_HW_MCU_NAME "\", " + #if MICROPY_PERSISTENT_CODE_LOAD_NATIVE + "\"architecture\": \"" MPY_FEATURE_ARCH_NAME "\", " + #endif "\"board_id\": \"" CIRCUITPY_BOARD_ID "\", " "\"creator_id\": %u, " "\"creation_id\": %u, " @@ -965,23 +963,17 @@ static void _reply_with_diskinfo_json(socketpool_socket_obj_t *socket, _request if (i > 0) { _send_chunk(socket, ","); } - fs_user_mount_t *fs = MP_OBJ_TO_PTR(vfs->obj); - // Skip non-fat and non-native block file systems. - if (fs->base.type != &mp_fat_vfs_type || (fs->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) == 0) { + supervisor_vfs_t *fs = MP_OBJ_TO_PTR(vfs->obj); + // Skip filesystems the supervisor can't work with (non-fat and + // non-native block file systems). + if (!supervisor_vfs_supported(fs)) { vfs = vfs->next; continue; } - DWORD free_clusters = 0; - FATFS *fatfs = &fs->fatfs; - f_getfree(fatfs, &free_clusters); - size_t ssize; - #if FF_MAX_SS != FF_MIN_SS - ssize = fatfs->ssize; - #else - ssize = FF_MIN_SS; - #endif - size_t block_size = fatfs->csize * ssize; - size_t total_size = fatfs->n_fatent - 2; + size_t block_size = 0; + size_t total_size = 0; + size_t free_blocks = 0; + supervisor_vfs_statfs(fs, &block_size, &total_size, &free_blocks); const char *writable = "false"; if (filesystem_lock(fs)) { @@ -993,7 +985,7 @@ static void _reply_with_diskinfo_json(socketpool_socket_obj_t *socket, _request "\"free\": %u, " "\"total\": %u, " "\"block_size\": %u, " - "\"writable\": %s}", vfs->str, free_clusters, total_size, block_size, writable); + "\"writable\": %s}", vfs->str, free_blocks, total_size, block_size, writable); i++; vfs = vfs->next; } @@ -1003,21 +995,6 @@ static void _reply_with_diskinfo_json(socketpool_socket_obj_t *socket, _request _send_chunk(socket, ""); } - -// FATFS has a two second timestamp resolution but the BLE API allows for nanosecond resolution. -// This function truncates the time the time to a resolution storable by FATFS and fills in the -// FATFS encoded version into fattime. -static uint64_t truncate_time(uint64_t input_time, DWORD *fattime) { - timeutils_struct_time_t tm; - uint64_t seconds_since_epoch = timeutils_seconds_since_epoch_from_nanoseconds_since_1970(input_time); - timeutils_seconds_since_epoch_to_struct_time(seconds_since_epoch, &tm); - uint64_t truncated_time = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970((seconds_since_epoch / 2) * 2 * 1000000000); - - *fattime = ((tm.tm_year - 1980) << 25) | (tm.tm_mon << 21) | (tm.tm_mday << 16) | - (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); - return truncated_time; -} - static void _discard_incoming(socketpool_socket_obj_t *socket, size_t amount) { size_t discarded = 0; while (discarded < amount) { @@ -1034,52 +1011,46 @@ static void _discard_incoming(socketpool_socket_obj_t *socket, size_t amount) { } } -static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *request, fs_user_mount_t *fs_mount, const TCHAR *path) { - FIL active_file; +static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *request, supervisor_vfs_t *fs_mount, const TCHAR *path) { + supervisor_vfs_file_t active_file; if (!filesystem_lock(fs_mount)) { _discard_incoming(socket, request->content_length); _reply_conflict(socket, request); return; } - if (request->timestamp_ms > 0) { - DWORD fattime; - truncate_time(request->timestamp_ms * 1000000, &fattime); - override_fattime(fattime); - } - FATFS *fs = &fs_mount->fatfs; - FRESULT result = f_open(fs, &active_file, path, FA_WRITE); + uint64_t mtime_ns = request->timestamp_ms > 0 ? (uint64_t)request->timestamp_ms * 1000000 : 0; + + supervisor_fs_err_t result = supervisor_vfs_open_file(fs_mount, path, SUPERVISOR_FS_OPEN_WRITE, mtime_ns, &active_file); bool new_file = false; size_t old_length = 0; - if (result == FR_NO_FILE) { + if (result == SUPERVISOR_FS_NO_FILE) { new_file = true; - result = f_open(fs, &active_file, path, FA_WRITE | FA_OPEN_ALWAYS); - } else if (result == FR_OK) { - old_length = f_size(&active_file); + result = supervisor_vfs_open_file(fs_mount, path, + SUPERVISOR_FS_OPEN_WRITE | SUPERVISOR_FS_OPEN_CREATE, mtime_ns, &active_file); + } else if (result == SUPERVISOR_FS_OK) { + old_length = supervisor_vfs_file_size(&active_file); } - if (result == FR_NO_PATH) { - override_fattime(0); + if (result == SUPERVISOR_FS_NO_PATH) { filesystem_unlock(fs_mount); _discard_incoming(socket, request->content_length); _reply_missing(socket, request); return; } - if (result == FR_WRITE_PROTECTED) { + if (result == SUPERVISOR_FS_WRITE_PROTECTED) { // The filesystem is held by something else with write access (most // commonly USB-MSC: the host has CIRCUITPY mounted, so CircuitPython // can't write through FatFS). Match the mkdir/move/delete paths and // reply 409 Conflict so clients can show an actionable message // ("eject CIRCUITPY / disable USB MSC") instead of a generic 500. - override_fattime(0); filesystem_unlock(fs_mount); _discard_incoming(socket, request->content_length); _reply_conflict(socket, request); return; } - if (result != FR_OK) { - override_fattime(0); + if (result != SUPERVISOR_FS_OK) { filesystem_unlock(fs_mount); _discard_incoming(socket, request->content_length); _reply_server_error(socket, request); @@ -1087,19 +1058,18 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req } // Change the file size to start. - f_lseek(&active_file, request->content_length); - if (f_tell(&active_file) < request->content_length) { + supervisor_vfs_seek_file(&active_file, request->content_length); + if (supervisor_vfs_tell_file(&active_file) < request->content_length) { if (!new_file) { // Truncate the file back to the old length. - f_lseek(&active_file, old_length); - f_truncate(&active_file); + supervisor_vfs_seek_file(&active_file, old_length); + supervisor_vfs_truncate_file(&active_file); } - f_close(&active_file); + supervisor_vfs_close_file(&active_file); if (new_file) { - f_unlink(fs, path); + supervisor_vfs_unlink(fs_mount, path); } - override_fattime(0); filesystem_unlock(fs_mount); // Too large. if (request->expect) { @@ -1112,8 +1082,10 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req } else if (request->expect) { _reply_continue(socket, request); } - f_truncate(&active_file); - f_rewind(&active_file); + // Truncate to content_length (current offset) and rewind so that the body + // overwrites the file from the start. + supervisor_vfs_truncate_file(&active_file); + supervisor_vfs_seek_file(&active_file, 0); size_t total_read = 0; bool error = false; @@ -1129,18 +1101,17 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req break; } total_read += len; - UINT actual; - f_write(&active_file, bytes, len, &actual); - if (actual < (UINT)len) { + size_t actual; + supervisor_fs_err_t write_result = supervisor_vfs_write_file(&active_file, bytes, len, &actual); + if (actual < (size_t)len || write_result != SUPERVISOR_FS_OK) { error = true; break; } } - f_close(&active_file); + supervisor_vfs_close_file(&active_file); filesystem_unlock(fs_mount); - override_fattime(0); if (error) { _discard_incoming(socket, request->content_length - total_read); _reply_server_error(socket, request); @@ -1286,12 +1257,12 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { // Delete is almost identical for files and directories so share the // implementation. if (strcasecmp(request->method, "DELETE") == 0) { - FRESULT result = supervisor_workflow_delete_recursive(path); - if (result == FR_WRITE_PROTECTED) { + supervisor_fs_err_t result = supervisor_workflow_delete_recursive(path); + if (result == SUPERVISOR_FS_WRITE_PROTECTED) { _reply_conflict(socket, request); - } else if (result == FR_NO_PATH || result == FR_NO_FILE) { + } else if (result == SUPERVISOR_FS_NO_PATH || result == SUPERVISOR_FS_NO_FILE) { _reply_missing(socket, request); - } else if (result != FR_OK) { + } else if (result != SUPERVISOR_FS_OK) { _reply_server_error(socket, request); } else { _reply_no_content(socket, request); @@ -1306,14 +1277,14 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { destination[destinationlen - 1] = '\0'; } - FRESULT result = supervisor_workflow_move(path, destination); - if (result == FR_WRITE_PROTECTED) { + supervisor_fs_err_t result = supervisor_workflow_move(path, destination); + if (result == SUPERVISOR_FS_WRITE_PROTECTED) { _reply_conflict(socket, request); - } else if (result == FR_EXIST) { // File exists and won't be overwritten. + } else if (result == SUPERVISOR_FS_EXIST) { // File exists and won't be overwritten. _reply_precondition_failed(socket, request); - } else if (result == FR_NO_PATH || result == FR_NO_FILE) { // Missing higher directories or target file. + } else if (result == SUPERVISOR_FS_NO_PATH || result == SUPERVISOR_FS_NO_FILE) { // Missing higher directories or target file. _reply_missing(socket, request); - } else if (result != FR_OK) { + } else if (result != SUPERVISOR_FS_OK) { _reply_server_error(socket, request); } else { _reply_created(socket, request); @@ -1321,18 +1292,15 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } return false; } else if (directory && strcasecmp(request->method, "PUT") == 0) { - DWORD fattime = 0; - if (request->timestamp_ms > 0) { - truncate_time(request->timestamp_ms * 1000000, &fattime); - } - FRESULT result = supervisor_workflow_mkdir_parents(fattime, path); - if (result == FR_WRITE_PROTECTED) { + uint64_t mtime_ns = request->timestamp_ms > 0 ? (uint64_t)request->timestamp_ms * 1000000 : 0; + supervisor_fs_err_t result = supervisor_workflow_mkdir_parents(mtime_ns, path); + if (result == SUPERVISOR_FS_WRITE_PROTECTED) { _reply_conflict(socket, request); - } else if (result == FR_EXIST) { + } else if (result == SUPERVISOR_FS_EXIST) { _reply_no_content(socket, request); - } else if (result == FR_NO_PATH) { + } else if (result == SUPERVISOR_FS_NO_PATH) { _reply_missing(socket, request); - } else if (result != FR_OK) { + } else if (result != SUPERVISOR_FS_OK) { _reply_server_error(socket, request); } else { _reply_created(socket, request); @@ -1342,43 +1310,28 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { } // These responses don't use helpers because they stream data in and - // out. So, share the mount lookup code. + // out. So, share the mount lookup code. filesystem_for_path handles + // both the root filesystem and additional mounts, whether they are + // FAT or littlefs. const char *path_out = NULL; - mp_vfs_mount_t *vfs = mp_vfs_lookup_path(path, &path_out); - if (vfs == MP_VFS_NONE) { + supervisor_vfs_t *fs_mount = filesystem_for_path(path, &path_out); + if (fs_mount == NULL || !supervisor_vfs_supported(fs_mount)) { _reply_missing(socket, request); return false; } - fs_user_mount_t *fs_mount; - if (vfs == MP_VFS_ROOT) { - fs_mount = filesystem_circuitpy(); - } else { - fs_mount = MP_OBJ_TO_PTR(vfs->obj); - // Skip non-fat and non-native block file systems. - if (!filesystem_native_fatfs(fs_mount)) { - _reply_missing(socket, request); - return false; - } - // Check if the vfs name is one character long: it must be "/" in that case. - // If so don't remove the mount point name. We must use an absolute path - // because otherwise the path will be adjusted by os.getcwd() when it's looked up. - if (strlen(vfs->str) != 1) { - // Remove the mount point directory name, such as "/sd". - path += strlen(vfs->str); - } - pathlen = strlen(path); + path = (char *)path_out; + pathlen = strlen(path); - } - FATFS *fs = &fs_mount->fatfs; if (directory) { if (strcasecmp(request->method, "GET") == 0) { - FF_DIR dir; - FRESULT res = f_opendir(fs, &dir, path); + supervisor_vfs_dir_t dir; + memset(&dir, 0, sizeof(dir)); + supervisor_fs_err_t res = supervisor_vfs_opendir(fs_mount, path, &dir); // Put the / back for replies. if (pathlen > 1) { path[pathlen - 1] = '/'; } - if (res != FR_OK) { + if (res != SUPERVISOR_FS_OK) { _reply_missing(socket, request); return false; } @@ -1390,20 +1343,20 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { _reply_missing(socket, request); } - f_closedir(&dir); + supervisor_vfs_closedir(&dir); } } else { // Dealing with a file. if (strcasecmp(request->method, "GET") == 0) { - FIL active_file; - FRESULT result = f_open(fs, &active_file, path, FA_READ); + supervisor_vfs_file_t active_file; + supervisor_fs_err_t result = supervisor_vfs_open_file(fs_mount, path, SUPERVISOR_FS_OPEN_READ, 0, &active_file); - if (result != FR_OK) { + if (result != SUPERVISOR_FS_OK) { _reply_missing(socket, request); } else { _reply_with_file(socket, request, path, &active_file); } - f_close(&active_file); + supervisor_vfs_close_file(&active_file); } else if (strcasecmp(request->method, "PUT") == 0) { _write_file_and_reply(socket, request, fs_mount, path); return true; diff --git a/supervisor/shared/workflow.c b/supervisor/shared/workflow.c index 21dd0512eb1..67cf14e68a7 100644 --- a/supervisor/shared/workflow.c +++ b/supervisor/shared/workflow.c @@ -4,12 +4,10 @@ // // SPDX-License-Identifier: MIT -#include #include "py/mpconfig.h" #include "py/mpstate.h" #include "py/stackctrl.h" #include "supervisor/background_callback.h" -#include "supervisor/fatfs.h" #include "supervisor/filesystem.h" #include "supervisor/workflow.h" #include "supervisor/shared/serial.h" @@ -121,138 +119,139 @@ void supervisor_workflow_start(void) { #endif } -FRESULT supervisor_workflow_move(const char *old_path, const char *new_path) { +supervisor_fs_err_t supervisor_workflow_move(const char *old_path, const char *new_path) { const char *old_mount_path; const char *new_mount_path; - fs_user_mount_t *active_mount = filesystem_for_path(old_path, &old_mount_path); - fs_user_mount_t *new_mount = filesystem_for_path(new_path, &new_mount_path); - if (active_mount == NULL || new_mount == NULL || active_mount != new_mount || !filesystem_native_fatfs(active_mount)) { - return FR_NO_PATH; + supervisor_vfs_t *active_mount = filesystem_for_path(old_path, &old_mount_path); + supervisor_vfs_t *new_mount = filesystem_for_path(new_path, &new_mount_path); + if (active_mount == NULL || new_mount == NULL || active_mount != new_mount || !supervisor_vfs_supported(active_mount)) { + return SUPERVISOR_FS_NO_PATH; } if (!filesystem_lock(active_mount)) { - return FR_WRITE_PROTECTED; + return SUPERVISOR_FS_WRITE_PROTECTED; } - FATFS *fs = &active_mount->fatfs; - FRESULT result = f_rename(fs, old_mount_path, new_mount_path); + supervisor_fs_err_t result = supervisor_vfs_rename(active_mount, old_mount_path, new_mount_path); filesystem_unlock(active_mount); return result; } -FRESULT supervisor_workflow_mkdir(DWORD fattime, const char *full_path) { +supervisor_fs_err_t supervisor_workflow_mkdir(uint64_t mtime_ns, const char *full_path) { const char *mount_path; - fs_user_mount_t *active_mount = filesystem_for_path(full_path, &mount_path); - if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { - return FR_NO_PATH; + supervisor_vfs_t *active_mount = filesystem_for_path(full_path, &mount_path); + if (active_mount == NULL || !supervisor_vfs_supported(active_mount)) { + return SUPERVISOR_FS_NO_PATH; } // If there is a mount on the directory, then the mount_path will be empty. - if (strlen(mount_path) == 0) { - return FR_EXIST; + if (strlen(mount_path) == 0 || strcmp(mount_path, "/") == 0) { + return SUPERVISOR_FS_EXIST; } // Check to see if the directory exists already. We don't care about writing // it if it already exists. - FATFS *fs = &active_mount->fatfs; - FILINFO file; - FRESULT result = f_stat(fs, mount_path, &file); - if (result == FR_OK) { - return FR_EXIST; + supervisor_fs_err_t result = supervisor_vfs_stat(active_mount, mount_path, NULL, NULL, NULL); + if (result == SUPERVISOR_FS_OK) { + return SUPERVISOR_FS_EXIST; } if (!filesystem_lock(active_mount)) { - return FR_WRITE_PROTECTED; + return SUPERVISOR_FS_WRITE_PROTECTED; } - override_fattime(fattime); - result = f_mkdir(fs, mount_path); - override_fattime(0); + result = supervisor_vfs_mkdir(active_mount, mount_path, mtime_ns); filesystem_unlock(active_mount); return result; } -FRESULT supervisor_workflow_mkdir_parents(DWORD fattime, char *path) { - override_fattime(fattime); - FRESULT result = FR_OK; +supervisor_fs_err_t supervisor_workflow_mkdir_parents(uint64_t mtime_ns, char *path) { + supervisor_fs_err_t result = SUPERVISOR_FS_OK; // Make parent directories. for (size_t j = 1; j < strlen(path); j++) { if (path[j] == '/') { path[j] = '\0'; - result = supervisor_workflow_mkdir(fattime, path); + result = supervisor_workflow_mkdir(mtime_ns, path); path[j] = '/'; - if (result != FR_OK && result != FR_EXIST) { + if (result != SUPERVISOR_FS_OK && result != SUPERVISOR_FS_EXIST) { break; } } } // Make the target directory. - if (result == FR_OK || result == FR_EXIST) { - result = supervisor_workflow_mkdir(fattime, path); - // This may return FR_EXIST when a file with the same name already exists. + if (result == SUPERVISOR_FS_OK || result == SUPERVISOR_FS_EXIST) { + result = supervisor_workflow_mkdir(mtime_ns, path); + // This may return SUPERVISOR_FS_EXIST when a file with the same name already exists. // FATFS does the same thing. } - override_fattime(0); return result; } -static FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path) { - FF_DIR dir; - FILINFO file_info; +static supervisor_fs_err_t supervisor_workflow_delete_directory_contents(supervisor_vfs_t *active_mount, const char *path) { // Check the stack since we're putting paths on it. if (mp_stack_usage() >= MP_STATE_THREAD(stack_limit)) { - return FR_INT_ERR; + return SUPERVISOR_FS_IO; } - FRESULT res = FR_OK; - while (res == FR_OK) { - res = f_opendir(fs, &dir, path); - if (res != FR_OK) { + supervisor_vfs_dir_t dir; + memset(&dir, 0, sizeof(dir)); + char name[FF_MAX_LFN + 1]; + supervisor_fs_err_t res = SUPERVISOR_FS_OK; + while (res == SUPERVISOR_FS_OK) { + res = supervisor_vfs_opendir(active_mount, path, &dir); + if (res != SUPERVISOR_FS_OK) { break; } - res = f_readdir(&dir, &file_info); + res = supervisor_vfs_readdir(&dir, name, sizeof(name), NULL, NULL, NULL); // We close and reopen the directory every time since we're deleting // entries and it may invalidate the directory handle. - f_closedir(&dir); - if (res != FR_OK || file_info.fname[0] == '\0') { + supervisor_vfs_closedir(&dir); + if (res != SUPERVISOR_FS_OK || name[0] == '\0') { break; } size_t pathlen = strlen(path); - size_t fnlen = strlen(file_info.fname); - TCHAR full_path[pathlen + 1 + fnlen]; + size_t fnlen = strlen(name); + char full_path[pathlen + 1 + fnlen + 1]; memcpy(full_path, path, pathlen); full_path[pathlen] = '/'; - size_t full_pathlen = pathlen + 1 + fnlen; - memcpy(full_path + pathlen + 1, file_info.fname, fnlen); - full_path[full_pathlen] = '\0'; - if ((file_info.fattrib & AM_DIR) != 0) { - res = supervisor_workflow_delete_directory_contents(fs, full_path); + memcpy(full_path + pathlen + 1, name, fnlen + 1); + if (path[pathlen - 1] == '/') { + // Trim the extra slash we put in. This happens when path is "/". + memmove(full_path + pathlen, name, fnlen + 1); } - if (res != FR_OK) { + bool is_dir = false; + res = supervisor_vfs_stat(active_mount, full_path, &is_dir, NULL, NULL); + if (res != SUPERVISOR_FS_OK) { break; } - res = f_unlink(fs, full_path); + if (is_dir) { + res = supervisor_workflow_delete_directory_contents(active_mount, full_path); + if (res != SUPERVISOR_FS_OK) { + break; + } + } + res = supervisor_vfs_unlink(active_mount, full_path); } - f_closedir(&dir); + supervisor_vfs_closedir(&dir); return res; } -FRESULT supervisor_workflow_delete_recursive(const char *full_path) { +supervisor_fs_err_t supervisor_workflow_delete_recursive(const char *full_path) { const char *mount_path; - fs_user_mount_t *active_mount = filesystem_for_path(full_path, &mount_path); - if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { - return FR_NO_PATH; + supervisor_vfs_t *active_mount = filesystem_for_path(full_path, &mount_path); + if (active_mount == NULL || !supervisor_vfs_supported(active_mount)) { + return SUPERVISOR_FS_NO_PATH; } if (!filesystem_lock(active_mount)) { - return FR_WRITE_PROTECTED; + return SUPERVISOR_FS_WRITE_PROTECTED; } - FATFS *fs = &active_mount->fatfs; - FILINFO file; - FRESULT result = f_stat(fs, mount_path, &file); - if (result == FR_OK) { - if ((file.fattrib & AM_DIR) != 0) { - result = supervisor_workflow_delete_directory_contents(fs, mount_path); + supervisor_fs_err_t result = SUPERVISOR_FS_OK; + bool is_dir = false; + result = supervisor_vfs_stat(active_mount, mount_path, &is_dir, NULL, NULL); + if (result == SUPERVISOR_FS_OK) { + if (is_dir) { + result = supervisor_workflow_delete_directory_contents(active_mount, mount_path); } - if (result == FR_OK) { - result = f_unlink(fs, mount_path); + if (result == SUPERVISOR_FS_OK) { + result = supervisor_vfs_unlink(active_mount, mount_path); } } filesystem_unlock(active_mount); diff --git a/supervisor/shared/workflow.h b/supervisor/shared/workflow.h index e03f9c00573..0698e2edebe 100644 --- a/supervisor/shared/workflow.h +++ b/supervisor/shared/workflow.h @@ -6,12 +6,16 @@ #pragma once -#include "lib/oofatfs/ff.h" +#include + +#include "supervisor/filesystem.h" extern bool supervisor_workflow_connecting(void); -// File system helpers for workflow code. -FRESULT supervisor_workflow_move(const char *old_path, const char *new_path); -FRESULT supervisor_workflow_mkdir(DWORD fattime, const char *full_path); -FRESULT supervisor_workflow_mkdir_parents(DWORD fattime, char *path); -FRESULT supervisor_workflow_delete_recursive(const char *full_path); +// File system helpers for workflow code. All of them take full paths (such as +// "/code.py"), resolve them to a mount and return supervisor_fs_err_t. Paths +// may name either FAT or littlefs mounts. +supervisor_fs_err_t supervisor_workflow_move(const char *old_path, const char *new_path); +supervisor_fs_err_t supervisor_workflow_mkdir(uint64_t mtime_ns, const char *full_path); +supervisor_fs_err_t supervisor_workflow_mkdir_parents(uint64_t mtime_ns, char *path); +supervisor_fs_err_t supervisor_workflow_delete_recursive(const char *full_path); diff --git a/supervisor/stub/filesystem.c b/supervisor/stub/filesystem.c index ce9204b95c3..7534f4e3400 100644 --- a/supervisor/stub/filesystem.c +++ b/supervisor/stub/filesystem.c @@ -4,6 +4,9 @@ // // SPDX-License-Identifier: MIT +#include +#include + #include "supervisor/filesystem.h" @@ -29,13 +32,13 @@ void filesystem_set_internal_writable_by_usb(bool writable) { return; } -void filesystem_set_writable_by_usb(fs_user_mount_t *vfs, bool usb_writable) { +void filesystem_set_writable_by_usb(supervisor_vfs_t *vfs, bool usb_writable) { (void)vfs; (void)usb_writable; return; } -bool filesystem_is_writable_by_python(fs_user_mount_t *vfs) { +bool filesystem_is_writable_by_python(supervisor_vfs_t *vfs) { (void)vfs; return true; } @@ -49,7 +52,7 @@ void filesystem_set_internal_concurrent_write_protection(bool concurrent_write_p return; } -void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concurrent_write_protection) { +void filesystem_set_concurrent_write_protection(supervisor_vfs_t *vfs, bool concurrent_write_protection) { (void)vfs; (void)concurrent_write_protection; return; @@ -58,3 +61,137 @@ void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concu bool filesystem_present(void) { return false; } + +// Without a filesystem, nothing is supported and all file operations fail. +bool supervisor_vfs_supported(supervisor_vfs_t *fs_mount) { + (void)fs_mount; + return false; +} + +supervisor_fs_err_t supervisor_vfs_open_file(supervisor_vfs_t *vfs, const char *path, uint32_t flags, + uint64_t mtime_ns, supervisor_vfs_file_t *file) { + (void)vfs; + (void)path; + (void)flags; + (void)mtime_ns; + (void)file; + return SUPERVISOR_FS_IO; +} + +supervisor_fs_err_t supervisor_vfs_close_file(supervisor_vfs_file_t *file) { + (void)file; + return SUPERVISOR_FS_OK; +} + +supervisor_fs_err_t supervisor_vfs_read_file(supervisor_vfs_file_t *file, void *buf, size_t len, size_t *bytes_read) { + (void)file; + (void)buf; + (void)len; + *bytes_read = 0; + return SUPERVISOR_FS_IO; +} + +supervisor_fs_err_t supervisor_vfs_write_file(supervisor_vfs_file_t *file, const void *buf, size_t len, size_t *bytes_written) { + (void)file; + (void)buf; + (void)len; + *bytes_written = 0; + return SUPERVISOR_FS_IO; +} + +supervisor_fs_err_t supervisor_vfs_seek_file(supervisor_vfs_file_t *file, size_t offset) { + (void)file; + (void)offset; + return SUPERVISOR_FS_IO; +} + +size_t supervisor_vfs_tell_file(supervisor_vfs_file_t *file) { + (void)file; + return 0; +} + +size_t supervisor_vfs_file_size(supervisor_vfs_file_t *file) { + (void)file; + return 0; +} + +supervisor_fs_err_t supervisor_vfs_truncate_file(supervisor_vfs_file_t *file) { + (void)file; + return SUPERVISOR_FS_IO; +} + +supervisor_fs_err_t supervisor_vfs_stat(supervisor_vfs_t *vfs, const char *path, bool *is_dir, size_t *size, uint64_t *mtime_ns) { + (void)vfs; + (void)path; + if (is_dir != NULL) { + *is_dir = false; + } + if (size != NULL) { + *size = 0; + } + if (mtime_ns != NULL) { + *mtime_ns = 0; + } + return SUPERVISOR_FS_NO_FILE; +} + +supervisor_fs_err_t supervisor_vfs_mkdir(supervisor_vfs_t *vfs, const char *path, uint64_t mtime_ns) { + (void)vfs; + (void)path; + (void)mtime_ns; + return SUPERVISOR_FS_IO; +} + +supervisor_fs_err_t supervisor_vfs_rename(supervisor_vfs_t *vfs, const char *old_path, const char *new_path) { + (void)vfs; + (void)old_path; + (void)new_path; + return SUPERVISOR_FS_IO; +} + +supervisor_fs_err_t supervisor_vfs_unlink(supervisor_vfs_t *vfs, const char *path) { + (void)vfs; + (void)path; + return SUPERVISOR_FS_IO; +} + +supervisor_fs_err_t supervisor_vfs_opendir(supervisor_vfs_t *vfs, const char *path, supervisor_vfs_dir_t *dir) { + (void)vfs; + (void)path; + (void)dir; + return SUPERVISOR_FS_NO_FILE; +} + +supervisor_fs_err_t supervisor_vfs_readdir(supervisor_vfs_dir_t *dir, char *name, size_t name_len, bool *is_dir, size_t *size, uint64_t *mtime_ns) { + (void)dir; + (void)name_len; + if (is_dir != NULL) { + *is_dir = false; + } + if (size != NULL) { + *size = 0; + } + if (mtime_ns != NULL) { + *mtime_ns = 0; + } + name[0] = '\0'; + return SUPERVISOR_FS_OK; +} + +supervisor_fs_err_t supervisor_vfs_rewinddir(supervisor_vfs_dir_t *dir) { + (void)dir; + return SUPERVISOR_FS_IO; +} + +supervisor_fs_err_t supervisor_vfs_closedir(supervisor_vfs_dir_t *dir) { + (void)dir; + return SUPERVISOR_FS_OK; +} + +supervisor_fs_err_t supervisor_vfs_statfs(supervisor_vfs_t *vfs, size_t *block_size, size_t *total_blocks, size_t *free_blocks) { + (void)vfs; + (void)block_size; + (void)total_blocks; + (void)free_blocks; + return SUPERVISOR_FS_IO; +} diff --git a/tests/README.md b/tests/README.md index 534e7e0a059..4edd3e1088e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -3,6 +3,17 @@ This directory contains tests for most parts of MicroPython. To run it you will need CPython 3.8.2 or newer, which is used to validate MicroPython's behaviour. +The tests are organized into several categories: +- Unit and regression tests: tests for MicroPython's core functionality, including the + compiler, runtime, and built-in modules +- perf_bench: performance benchmarks +- internal_bench: internal performance benchmarks +- Serial reliability and performance test +- Test key/certificates +- CPython vs MicroPython Differences + +## Unit and regression tests + To run all stable tests, run the "run-tests.py" script in this directory. By default that will run the test suite against the unix port of MicroPython. @@ -63,7 +74,7 @@ module, should go in the import/ subdirectory. The `perf_bench` directory contains some performance benchmarks that can be used to benchmark different MicroPython firmwares or host ports. -The runner utility is `run-perfbench,py`. Execute `./run-perfbench.py --help` +The runner utility is `run-perfbench.py`. Execute `./run-perfbench.py --help` for a full list of command line options. ### Benchmarking a target @@ -249,3 +260,87 @@ $ openssl ecparam -name prime256v1 -genkey -noout -out ec_key.pem $ openssl pkey -in ec_key.pem -out ec_key.der -outform DER $ openssl req -new -x509 -key ec_key.pem -out ec_cert.der -outform DER -days 3650 -nodes -subj '/CN=micropython.local/O=MicroPython/C=AU' ``` + +## CPython vs MicroPython Differences + +The `tests/cpydiff` folder contains test files that document and verify the differences +between the CPython and MicroPython implementations. + +These tests are designed to: +- Execute the same code on both CPython and MicroPython +- Document behavioral differences between the two implementations +- Generate documentation pages that help users understand these differences + +### How It Works + +1. Each test file contains Python code that demonstrates a specific difference. +2. The tests are executed on both CPython and MicroPython. +3. The output from both implementations is captured and compared. If the outputs are the + same, the generation will fail (because the outputs should be different). +4. The results, along with metadata from the file docstrings, are used to generate + documentation. + +### Documentation Generation + +The documentation is automatically generated using: +``` +tools/gen-cpydiff.py +``` + +This script: +- Parses the docstring metadata from each test file +- Runs the tests on both implementations +- Combines the results to create comprehensive documentation pages +- Outputs formatted reStructuredText documentation showing the differences + +**Note:** This script is automatically executed as part of the documentation publishing +process when building the docs. + +### Test File Format + +The test filename should match the categories of the test. For example, a test with +categories `Syntax,Operators` should be named `syntax_operators_*.py`. + +Each test file should include a docstring with the following format, and include a +minimal code snippet that reproduces the difference when run on both CPython and the +unix port of MicroPython: + +```python +""" +categories: Category,Subcategory +description: Brief description of the difference being tested +cause: Explanation of why this difference exists +workaround: How to work around this difference (or "Unknown" if none) +""" +# Minimal Python code reproducing the difference +import sys +print(sys.implementation.name) +``` + +The categories and subcategories are used to organize the documentation into sections. +Files with the same category and/or subcategory will be placed in the same section. + +Common categories include: +- Syntax +- Core (Core language) +- Types (Builtin types) +- Modules + +### Building the Documentation + +The documentation is automatically regenerated during the documentation build process. +To manually regenerate after adding or modifying tests: + +1. Set environment variables if needed: + - `MICROPY_MICROPYTHON`: Path to MicroPython executable + - `MICROPY_CPYTHON3`: Path to CPython 3.x executable (default: "python3") + +2. Run the generation script from the project root: + ``` + python tools/gen-cpydiff.py + ``` + +The generated documentation will be placed in `docs/genrst/` as reStructuredText files. + +Also see `docs/README.md` for more information on building the documentation locally +to validate the rendering of the resulting documentation page(s). diff --git a/tests/assets/Makefile b/tests/assets/Makefile new file mode 100644 index 00000000000..6048ee2a405 --- /dev/null +++ b/tests/assets/Makefile @@ -0,0 +1,29 @@ +MPY_DIR = ../.. + +PYTHON := $(command -v python3 2> /dev/null) +ifndef PYTHON +PYTHON = python +endif + +# Files created with dd if=/dev/urandom ... - the file name is the CRC32 +# of the data contained in the file itself. + +ROMFS_FILES = romfs_source/0x30d83fe5.bin \ + romfs_source/0x37bef0eb.bin \ + romfs_source/0x442f3b5f.bin \ + romfs_source/0x648793fb.bin \ + romfs_source/0x913837b6.bin \ + romfs_source/0xdb14aac7.bin \ + romfs_source/romfs_sentinel.txt + +.PHONY: romfs +romfs: all + +.PHONY: clean +clean: + rm -f random_romfs.bin + +all: random_romfs.bin + +random_romfs.bin: $(ROMFS_FILES) + $(PYTHON) $(MPY_DIR)/tools/mpremote/mpremote.py romfs --partition 0 --no-mpy --output $@ build romfs_source diff --git a/tests/assets/README.md b/tests/assets/README.md new file mode 100644 index 00000000000..a5f935ee1b9 --- /dev/null +++ b/tests/assets/README.md @@ -0,0 +1,8 @@ +This directory contains assets for other tests to use: + +- A .mpy built against the current .mpy version that can be used to test + freezing without a dependency on mpy-cross (`frozentest.py` and + `frozentest.mpy`) +- A ROMFS image containing random binary files for testing mounting and reading + from the partition without having to generate a custom image + (`random_romfs.bin`, the `romfs_source` directory, and `Makefile`) diff --git a/tests/frozen/frozentest.mpy b/tests/assets/frozentest.mpy similarity index 100% rename from tests/frozen/frozentest.mpy rename to tests/assets/frozentest.mpy diff --git a/tests/frozen/frozentest.py b/tests/assets/frozentest.py similarity index 100% rename from tests/frozen/frozentest.py rename to tests/assets/frozentest.py diff --git a/tests/assets/random_romfs.bin b/tests/assets/random_romfs.bin new file mode 100644 index 00000000000..387bd7ec476 Binary files /dev/null and b/tests/assets/random_romfs.bin differ diff --git a/tests/assets/romfs_source/0x30d83fe5.bin b/tests/assets/romfs_source/0x30d83fe5.bin new file mode 100644 index 00000000000..625e51fb8ae Binary files /dev/null and b/tests/assets/romfs_source/0x30d83fe5.bin differ diff --git a/tests/assets/romfs_source/0x37bef0eb.bin b/tests/assets/romfs_source/0x37bef0eb.bin new file mode 100644 index 00000000000..11e1c67871c Binary files /dev/null and b/tests/assets/romfs_source/0x37bef0eb.bin differ diff --git a/tests/assets/romfs_source/0x442f3b5f.bin b/tests/assets/romfs_source/0x442f3b5f.bin new file mode 100644 index 00000000000..ffe9f8f0e2c Binary files /dev/null and b/tests/assets/romfs_source/0x442f3b5f.bin differ diff --git a/tests/assets/romfs_source/0x648793fb.bin b/tests/assets/romfs_source/0x648793fb.bin new file mode 100644 index 00000000000..f99b58c610a Binary files /dev/null and b/tests/assets/romfs_source/0x648793fb.bin differ diff --git a/tests/assets/romfs_source/0x913837b6.bin b/tests/assets/romfs_source/0x913837b6.bin new file mode 100644 index 00000000000..6511184346a Binary files /dev/null and b/tests/assets/romfs_source/0x913837b6.bin differ diff --git a/tests/assets/romfs_source/0xdb14aac7.bin b/tests/assets/romfs_source/0xdb14aac7.bin new file mode 100644 index 00000000000..51754d4ecd8 Binary files /dev/null and b/tests/assets/romfs_source/0xdb14aac7.bin differ diff --git a/tests/assets/romfs_source/romfs_sentinel.txt b/tests/assets/romfs_source/romfs_sentinel.txt new file mode 100644 index 00000000000..6ff9311d70e --- /dev/null +++ b/tests/assets/romfs_source/romfs_sentinel.txt @@ -0,0 +1,3 @@ +*MPY-ROMFS-TEST-PARTITION* + +This is a ROMFS partition to be used in MicroPython tests, usually for CI. diff --git a/tests/basics/array_int_repr.py b/tests/basics/array_int_repr.py new file mode 100644 index 00000000000..55a2d5998eb --- /dev/null +++ b/tests/basics/array_int_repr.py @@ -0,0 +1,85 @@ +# Test array integer representations in memory +# +# This has to be a unit test because correct internal representation depends on +# native endianness +# +# These test cases should pass on both CPython and MicroPython. + +try: + from array import array + from sys import byteorder +except ImportError: + print("SKIP") + raise SystemExit + +try: + import unittest +except MemoryError: + print("SKIP-TOO-LARGE") # some small boards can't fit unittest in RAM + raise SystemExit + +# Ports without bigint support don't support typecode 'q' +try: + array("q", []) + array_has_typecode_q = True +except: + array_has_typecode_q = False + + +class TestIntReprs(unittest.TestCase): + def _test_repr(self, typecode, values): + # create an array with the specified typecode and list of values + a = array(typecode, values) + a_hex = memoryview(a).hex() + print(a, a_hex) + + self.assertEqual(len(a_hex) % len(values), 0) + # no array.itemsize in MicroPython, so calculate item size + sz = len(a_hex) // 2 // len(values) + if hasattr(a, "itemsize"): + self.assertEqual(a.itemsize, sz) + + # build alternative hex representation of the array using int.to_bytes() + # on each value + values_hex = "" + for v in values: + v_bytes = v.to_bytes(sz, byteorder=byteorder, signed=typecode.islower()) + values_hex += v_bytes.hex() + + # compare with the raw array contents + self.assertEqual(a_hex, values_hex) + + def test_smaller_ints(self): + for typecode, initialiser in ( + ("b", [1, -1, 120, -120]), + ("B", [1, 5, 220]), + ("h", [5, -1, 32_000, -32_000]), + ("H", [5, 1, 32_000, 65_535]), + ("i", [5, -1, 32_000, -32_000]), # CPython only guarantees min 2 bytes, C style! + ("I", [5, 1, 32_000, 65_535]), + ("l", [5, -1, 2_000_000, -2_000_000, 0x7FFF_FFFF]), + ("L", [5, 1, 65_536, 2_000_000, 0x7FFF_FFFF, 0xFFFF_FFFF]), + ): + self._test_repr(typecode, initialiser) + + @unittest.skipIf(not array_has_typecode_q, "port has no bigint support") + def test_bigints(self): + # Note: need to be careful not to write any literal expressions that can't be compiled on non-bigint MP + a = 0x1FFF_FFF + b = 62 + + try: + # this calculation will trigger OverflowError if bigint is set to long long + max_uint64 = [2 ** (b + 1)] + except OverflowError: + max_uint64 = [] + + for typecode, initialiser in ( + ("q", [a * 5, -a * 10, 2**b, (2**b) * -1]), + ("Q", [a * 5, a * 10, 2**b, (2**b) - 1, (2**b) + 1] + max_uint64), + ): + self._test_repr(typecode, initialiser) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/basics/array_limits_intbig.py b/tests/basics/array_limits_intbig.py new file mode 100644 index 00000000000..ec740a87bed --- /dev/null +++ b/tests/basics/array_limits_intbig.py @@ -0,0 +1,124 @@ +# Test behaviour when array module is provided out of bounds values +# +# This test is intended to also pass on CPython. + +try: + from array import array +except ImportError: + print("SKIP") + raise SystemExit + +try: + import unittest +except MemoryError: + print("SKIP-TOO-LARGE") # some small boards can't fit unittest in RAM + raise SystemExit + + +# MicroPython V2.0 will enforce bounds on items (same as CPython), V1.x truncates +# +# Note: once `_. +# and tests/cpydiff/core_function_star.py + +try: + exec("f(y=1, *(3,))") +except SyntaxError as e: + print("SyntaxError") diff --git a/tests/basics/fun_callstar_kwarg.py.exp b/tests/basics/fun_callstar_kwarg.py.exp new file mode 100644 index 00000000000..8729fc43437 --- /dev/null +++ b/tests/basics/fun_callstar_kwarg.py.exp @@ -0,0 +1 @@ +SyntaxError diff --git a/tests/basics/gen_yield_from_close.py b/tests/basics/gen_yield_from_close.py index a493a410353..981c810564b 100644 --- a/tests/basics/gen_yield_from_close.py +++ b/tests/basics/gen_yield_from_close.py @@ -75,9 +75,9 @@ def gen6(): yield -1 try: print((yield from gen5())) - except GeneratorExit: + except GeneratorExit as e: print("delegating caught GeneratorExit") - raise + raise e yield 10 yield 11 diff --git a/tests/basics/generator_pend_throw.py b/tests/basics/generator_pend_throw.py index ae8c21189e9..6cecd418dd0 100644 --- a/tests/basics/generator_pend_throw.py +++ b/tests/basics/generator_pend_throw.py @@ -46,7 +46,7 @@ def gen_next(): try: next(g) except Exception as e: - print("raised", repr(e)) + print("raised {} {}".format(type(e), str(e) or "generator already executing")) # Verify that you can't pend_throw from within the running coroutine. @@ -59,7 +59,7 @@ def gen_pend_throw(): try: next(g) except Exception as e: - print("raised", repr(e)) + print("raised {} {}".format(type(e), str(e) or "generator already executing")) # Verify that the pend_throw exception can be ignored. diff --git a/tests/basics/generator_pend_throw.py.exp b/tests/basics/generator_pend_throw.py.exp index 8a3dadfec7a..56fa68be252 100644 --- a/tests/basics/generator_pend_throw.py.exp +++ b/tests/basics/generator_pend_throw.py.exp @@ -3,8 +3,8 @@ raised ValueError() ret was: None raised OSError() -raised ValueError('generator already executing',) -raised ValueError('generator already executing',) +raised generator already executing +raised generator already executing 0 ignore CancelledError 1 diff --git a/tests/basics/int_64_basics.py b/tests/basics/int_64_basics.py index ef76793317e..b9d46c7d701 100644 --- a/tests/basics/int_64_basics.py +++ b/tests/basics/int_64_basics.py @@ -145,12 +145,12 @@ try: print((1 << 48) >> -4) except ValueError as e: - print(e) + print(str(e) or "negative shift count") try: print((1 << 48) << -6) except ValueError as e: - print(e) + print(str(e) or "negative shift count") # Test that the most extreme 64 bit integer values all parse with int() print(int("-9223372036854775807")) diff --git a/tests/basics/int_bytes.py b/tests/basics/int_bytes.py index d1999bebb0e..7a1edc2ccec 100644 --- a/tests/basics/int_bytes.py +++ b/tests/basics/int_bytes.py @@ -1,11 +1,15 @@ -# CIRCUITPY-CHANGE: signed support +import sys + print((10).to_bytes(1, "little")) print((-10).to_bytes(1, "little", signed=True)) # Test fitting in length that's not a power of two. -print((0x10000).to_bytes(3, 'little')) +print((0x10000).to_bytes(3, "little")) print((111111).to_bytes(4, "little")) print((-111111).to_bytes(4, "little", signed=True)) print((100).to_bytes(10, "little")) +print(int.from_bytes(b"\x00\x01\0\0\0\0\0\0", "little")) +print(int.from_bytes(b"\x01\0\0\0\0\0\0\0", "little")) +print(int.from_bytes(b"\x00\x01\0\0\0\0\0\0", "little")) print((-100).to_bytes(10, "little", signed=True)) # check that extra zero bytes don't change the internal int value @@ -32,19 +36,6 @@ except OverflowError: print("OverflowError") -# CIRCUITPY-CHANGE: more tests -# too small buffer should raise an error -try: - (256).to_bytes(1, "little") -except OverflowError: - print("OverflowError") - -# negative numbers should raise an error if signed=False -try: - (-256).to_bytes(2, "little") -except OverflowError: - print("OverflowError") - # except for converting 0 to a zero-length byte array print((0).to_bytes(0, "big")) @@ -58,11 +49,54 @@ # OverFlowError if not big enough +try: + (0x123).to_bytes(1, "big") +except OverflowError: + print("OverflowError") + +try: + (0x12345).to_bytes(2, "big") +except OverflowError: + print("OverflowError") + +try: + (0x1234567).to_bytes(3, "big") +except OverflowError: + print("OverflowError") + + +# negative representations + +print((-1).to_bytes(1, "little", signed=True)) +print((-1).to_bytes(3, "little", signed=True)) +print((-1).to_bytes(1, "big", signed=True)) +print((-1).to_bytes(3, "big", signed=True)) +print((-128).to_bytes(1, "big", signed=True)) +print((-32768).to_bytes(2, "big", signed=True)) +print((-(1 << 23)).to_bytes(3, "big", signed=True)) + +# negative numbers should raise an error if signed=False, regardless of fitting or not try: (-256).to_bytes(2, "little", signed=False) except OverflowError: print("OverflowError") -# byteorder arg can be omitted; default is "big" -print(int.from_bytes(b"\x01\0")) -print((100).to_bytes(10)) +try: + (-1).to_bytes(1, "little") +except OverflowError: + print("OverflowError") + +try: + print((-129).to_bytes(1, "big")) +except OverflowError: + print("OverflowError") + +try: + print((-32769).to_bytes(2, "big")) +except OverflowError: + print("OverflowError") + +try: + print((-(1 << 23) - 1).to_bytes(2, "big")) +except OverflowError: + print("OverflowError") diff --git a/tests/basics/int_bytes_int64.py b/tests/basics/int_bytes_int64.py index 032dbccc5b1..2ca87f20dee 100644 --- a/tests/basics/int_bytes_int64.py +++ b/tests/basics/int_bytes_int64.py @@ -5,7 +5,7 @@ # long longs. try: - x = int.from_bytes(b"\x6F\xAB\xCD\x12\x34\x56\x78\xFB", "big") + x = int.from_bytes(b"\x6f\xab\xcd\x12\x34\x56\x78\xfb", "big") except OverflowError: print("SKIP") # Port can't represent this size of integer at all raise SystemExit @@ -36,17 +36,8 @@ # negative representations -# MicroPython int.to_bytes() behaves as if signed=True for negative numbers -if "micropython" in repr(sys.implementation): +x = -x - def to_bytes_compat(i, l, e): - return i.to_bytes(l, e) -else: - # Implement MicroPython compatible behaviour for CPython - def to_bytes_compat(i, l, e): - return i.to_bytes(l, e, signed=i < 0) - - -print(to_bytes_compat(-x, 8, "little")) -print(to_bytes_compat(-x, 20, "big")) -print(to_bytes_compat(-x, 20, "little")) +print(x.to_bytes(8, "little", signed=True)) +print(x.to_bytes(20, "big", signed=True)) +print(x.to_bytes(20, "little", signed=True)) diff --git a/tests/basics/int_bytes_intbig.py b/tests/basics/int_bytes_intbig.py index 073c8bf7892..6d3265ddb1d 100644 --- a/tests/basics/int_bytes_intbig.py +++ b/tests/basics/int_bytes_intbig.py @@ -4,11 +4,10 @@ import sys -# CIRCUITPY-CHANGE: signed support print((2**64).to_bytes(9, "little")) -print((-2**64).to_bytes(9, "little", signed=True)) +print((-(2**64)).to_bytes(9, "little", signed=True)) print((2**64).to_bytes(9, "big")) -print((-2**64).to_bytes(9, "big", signed=True)) +print((-(2**64)).to_bytes(9, "big", signed=True)) b = bytes(range(20)) @@ -26,50 +25,69 @@ # check that extra zero bytes don't change the internal int value print(int.from_bytes(b + bytes(10), "little") == int.from_bytes(b, "little")) -# CIRCUITPY-CHANGE: more tests -# too small buffer should raise an error +# can't write to a zero-length bytes object try: - (2**64).to_bytes(8, "little") + ib.to_bytes(0, "little") except OverflowError: print("OverflowError") -# negative numbers should raise an error if signed=False +# or one that is too short try: - (-2**64).to_bytes(9, "little") + ib.to_bytes(18, "big") except OverflowError: print("OverflowError") -# negative representations - -# MicroPython int.to_bytes() behaves as if signed=True for negative numbers -if "micropython" in repr(sys.implementation): - - def to_bytes_compat(i, l, e): - return i.to_bytes(l, e) -else: - # Implement MicroPython compatible behaviour for CPython - def to_bytes_compat(i, l, e): - return i.to_bytes(l, e, signed=i < 0) - - -print(to_bytes_compat(-ib, 20, "big")) -print(to_bytes_compat(ib * -ib, 40, "big")) - -# case where an additional byte is needed for sign bit -ib = (2**64) - 1 -print(ib.to_bytes(8, "little")) - -ib *= -1 - +# including when signed try: - (-2**64).to_bytes(9, "little", signed=False) + ib.to_bytes(18, "big", signed=True) except OverflowError: print("OverflowError") + +# negative representations + +# negative numbers should raise an error if signed=False try: - print(to_bytes_compat(ib, 8, "little")) + (-(2**64)).to_bytes(9, "little", signed=False) except OverflowError: print("OverflowError") -print(to_bytes_compat(ib, 9, "little")) -print(to_bytes_compat(ib, 9, "big")) +print((-ib).to_bytes(20, "big", signed=True)) +print((ib * -ib).to_bytes(40, "big", signed=True)) + +# cases where an additional byte is needed for sign bit + +MAX_U24 = (2**24) - 1 +MAX_U32 = (2**32) - 1 +MAX_U64 = (2**64) - 1 + +for ib, nbytes in ( + (-127, 1), + (255, 1), + (-255, 1), + (65535, 2), + (-65535, 2), + (-65534, 2), + (MAX_U24, 3), + (-MAX_U24, 3), + (1 - MAX_U24, 3), + (MAX_U32, 4), + (-MAX_U32, 4), + (1 - MAX_U32, 4), + (2 - MAX_U32, 4), + (MAX_U64, 8), + (-MAX_U64, 8), + (1 - MAX_U64, 8), + (2 - MAX_U64, 8), + (2 * MAX_U64, 8), + (-2 * MAX_U64, 8), +): + print("ib", hex(ib), "nbytes", nbytes, ":") + for signed in False, True: + for endian in "little", "big": + for nbytes_offs in (-1, 0, 1): + try: + as_bytes = ib.to_bytes(nbytes + nbytes_offs, endian, signed=signed) + except OverflowError: + as_bytes = "OverflowError" + print(as_bytes, "signed", signed, "endian", endian, "nbytes_offs", nbytes_offs) diff --git a/tests/basics/string_find.py b/tests/basics/string_find.py index f9fcad3e579..f37064ad291 100644 --- a/tests/basics/string_find.py +++ b/tests/basics/string_find.py @@ -24,6 +24,7 @@ print("aaaaaaaaaaa".find("bbb", 9, 2)) try: + # Only works on bytes/bytearray. 'abc'.find(1) except TypeError: print('TypeError') diff --git a/tests/basics/string_fstring.py b/tests/basics/string_fstring.py index daa687dbddb..03e2d90a8f7 100644 --- a/tests/basics/string_fstring.py +++ b/tests/basics/string_fstring.py @@ -83,12 +83,5 @@ def foo(a, b): print(fr"\r{x}") # Format specifiers with nested replacement fields -space = 5 -prec = 2 -print(f"{3.14:{space}.{prec}}") - -space_prec = "5.2" -print(f"{3.14:{space_prec}}") - radix = "x" print(f"{314:{radix}}") diff --git a/tests/basics/string_index.py b/tests/basics/string_index.py index 31f6900e6c1..328f2dff54c 100644 --- a/tests/basics/string_index.py +++ b/tests/basics/string_index.py @@ -76,3 +76,9 @@ print("Raised ValueError") else: print("Did not raise ValueError") + +try: + # Only works on bytes/bytearray. + 'abc'.index(1) +except TypeError: + print('TypeError') diff --git a/tests/basics/string_tstring_basic.py b/tests/basics/string_tstring_basic.py index e23a3f06595..aa9de327b59 100644 --- a/tests/basics/string_tstring_basic.py +++ b/tests/basics/string_tstring_basic.py @@ -172,7 +172,8 @@ print("\n=== Interpolation attribute tests ===") i_basic = Interpolation(42, "x") print(f"Basic conversion: {i_basic.conversion}") -print(f"Basic format_spec: {i_basic.format_spec}") +# Put in quotes to make empty string visible. +print(f"Basic format_spec: '{i_basic.format_spec}'") i_with_conv = Interpolation(42, "x", "s") print(f"With conversion: {i_with_conv.conversion}") diff --git a/tests/basics/string_tstring_basic.py.exp b/tests/basics/string_tstring_basic.py.exp index 4fcb3a75834..60affaad499 100644 --- a/tests/basics/string_tstring_basic.py.exp +++ b/tests/basics/string_tstring_basic.py.exp @@ -71,7 +71,7 @@ Nested expr: Template(strings=('', ''), interpolations=(Interpolation('{}', 'inn === Interpolation attribute tests === Basic conversion: None -Basic format_spec: +Basic format_spec: '' With conversion: s With format_spec: :>10 Full conversion: r diff --git a/tests/basics/string_tstring_basic1.py.exp b/tests/basics/string_tstring_basic1.py.exp index 52fc6f6c94c..7f2b0d85b60 100644 --- a/tests/basics/string_tstring_basic1.py.exp +++ b/tests/basics/string_tstring_basic1.py.exp @@ -16,14 +16,14 @@ Template(strings=('\\k',), interpolations=()) Invalid \x escape: SyntaxError Invalid \u escape: SyntaxError Invalid \U escape: SyntaxError -Template(strings=('\x00\x01\xff',), interpolations=()) +Template(strings=('\x00\x01ÿ',), interpolations=()) Template(strings=('A',), interpolations=()) -Template(strings=('\u03b1',), interpolations=()) -Template(strings=('\u2764',), interpolations=()) +Template(strings=('α',), interpolations=()) +Template(strings=('❤',), interpolations=()) Template(strings=('A',), interpolations=()) -Template(strings=('\U0001f600',), interpolations=()) +Template(strings=('😀',), interpolations=()) Template(strings=('ABC',), interpolations=()) -Unicode: Template(strings=('Unicode test:\nEmoji: ', '\nSpecial: ', ''), interpolations=(Interpolation('\U0001f40d', "'\\U0001f40d'", None, ''), Interpolation('\u03b1 \u03b2 \u03b3', "'\\u03b1 \\u03b2 \\u03b3'", None, ''))) +Unicode: Template(strings=('Unicode test:\nEmoji: ', '\nSpecial: ', ''), interpolations=(Interpolation('🐍', "'\\U0001f40d'", None, ''), Interpolation('α β γ', "'\\u03b1 \\u03b2 \\u03b3'", None, ''))) === Trailing whitespace preservation (PEP 750) === Expression with trailing spaces: |x| diff --git a/tests/basics/subclass_native_exc_new.py b/tests/basics/subclass_native_exc_new.py index a431392eaa4..87be6c76026 100644 --- a/tests/basics/subclass_native_exc_new.py +++ b/tests/basics/subclass_native_exc_new.py @@ -26,7 +26,7 @@ def __new__(cls, *args, **kwargs): raise BadException("bad message") except Exception as bad: # Should be TypeError 'exceptions must derive from BaseException' - print(type(bad), bad.args[0]) + print(type(bad), bad.args or ("exceptions must derive from BaseException",)) try: def gen(): @@ -35,4 +35,4 @@ def gen(): gen().throw(BadException) except Exception as genbad: # Should be TypeError 'exceptions must derive from BaseException' - print(type(genbad), genbad.args[0]) + print(type(genbad), genbad.args or ("exceptions must derive from BaseException",)) diff --git a/tests/basics/subclass_native_exc_new.py.exp b/tests/basics/subclass_native_exc_new.py.exp index 65709b2ccf2..d2b770e8aa1 100644 --- a/tests/basics/subclass_native_exc_new.py.exp +++ b/tests/basics/subclass_native_exc_new.py.exp @@ -1,6 +1,6 @@ GoodException __new__ good message BadException __new__ - exceptions must derive from BaseException + ('exceptions must derive from BaseException',) BadException __new__ - exceptions must derive from BaseException + ('exceptions must derive from BaseException',) diff --git a/tests/basics/tuple_subclass.py b/tests/basics/tuple_subclass.py new file mode 100644 index 00000000000..a8ee3ff1a4f --- /dev/null +++ b/tests/basics/tuple_subclass.py @@ -0,0 +1,62 @@ +try: + from collections import namedtuple +except ImportError: + print("SKIP") + raise SystemExit + + +class MyTuple(tuple): + pass + + +N = namedtuple("N", ("a", "b")) + + +class MyNamedTuple(N): + pass + + +t = (1, 2) +m = MyTuple((3, 4)) +n = N(5, 6) +q = MyNamedTuple(7, 8) + +print(t + t) +print(t + m) +print(t + n) +print(t + q) + +print(m + t) +print(m + m) +print(m + n) +print(m + q) + +print(n + t) +print(n + m) +print(n + n) +print(n + q) + +print(q + t) +print(q + m) +print(q + n) +print(q + q) + +print(t < t) +print(t < m) +print(t < n) +print(t < q) + +print(m < t) +print(m < m) +print(m < n) +print(m < q) + +print(n < t) +print(n < m) +print(n < n) +print(n < q) + +print(q < t) +print(q < m) +print(q < n) +print(q < q) diff --git a/tests/circuitpython/hmac.py b/tests/circuitpython/hmac.py new file mode 100644 index 00000000000..d909e7f7ee0 --- /dev/null +++ b/tests/circuitpython/hmac.py @@ -0,0 +1,83 @@ +try: + import hmac +except ImportError: + print("SKIP") + raise SystemExit + + +def hx(b): + return "".join("%02x" % c for c in b) + + +# RFC 4231 Test Case 1 +print(hmac.new(b"\x0b" * 20, b"Hi There", digestmod="sha256").hexdigest()) +print(hmac.new(b"\x0b" * 20, b"Hi There", digestmod="sha1").hexdigest()) + +# RFC 4231 Test Case 2 ("Jefe") +print(hmac.new(b"Jefe", b"what do ya want for nothing?", digestmod="sha256").hexdigest()) + +# RFC 4231 Test Case 7 (key longer than the block size) +long_key = b"\xaa" * 131 +long_data = ( + b"This is a test using a larger than block-size key and a larger than block-size " + b"data. The key needs to be hashed before being used by the HMAC algorithm." +) +print(hmac.new(long_key, long_data, digestmod="sha256").hexdigest()) + +# key exactly one block, and an empty message +print(hmac.new(b"k" * 64, b"msg", digestmod="sha256").hexdigest()) +print(hmac.new(b"key", b"", digestmod="sha256").hexdigest()) + +# incremental update matches one-shot +m = hmac.new(b"key", digestmod="sha256") +m.update(b"ab") +m.update(b"cde") +print(m.hexdigest() == hmac.new(b"key", b"abcde", digestmod="sha256").hexdigest()) + +# digest() finalizes: a repeated call returns the same cached bytes, but +# update() afterward is no longer allowed +x = hmac.new(b"key", b"1234", digestmod="sha256") +d1 = x.hexdigest() +d2 = x.hexdigest() +print(d1 == d2 == hmac.new(b"key", b"1234", digestmod="sha256").hexdigest()) +try: + x.update(b"more") +except RuntimeError: + print("RuntimeError") + +# copy() is not supported: PSA's multipart MAC operation can't be cloned +a = hmac.new(b"key", b"foo", digestmod="sha256") +try: + a.copy() +except NotImplementedError: + print("NotImplementedError") + +# digest() returns bytes; hexdigest() is its hex +h = hmac.new(b"key", b"abcde", digestmod="sha256") +print(hx(h.digest()) == h.hexdigest()) + +# one-shot module function +one_shot = hmac.digest(b"key", b"abcde", "sha256") +incremental = hmac.new(b"key", b"abcde", digestmod="sha256").digest() +print(one_shot == incremental) + +# metadata +s = hmac.new(b"k", digestmod="sha256") +print(s.digest_size, s.block_size, s.name) +s = hmac.new(b"k", digestmod="sha1") +print(s.digest_size, s.block_size, s.name) + +# unsupported algorithm +try: + hmac.new(b"k", digestmod="md5") +except ValueError: + print("ValueError") + +# compare_digest +good = hmac.new(b"key", b"msg", digestmod="sha256").digest() +bad = bytearray(good) +bad[0] ^= 1 +print(hmac.compare_digest(good, bytes(good))) +print(hmac.compare_digest(good, bytes(bad))) +print(hmac.compare_digest(good, good[:-1])) +print(hmac.compare_digest(memoryview(good), bytearray(good))) diff --git a/tests/circuitpython/hmac.py.exp b/tests/circuitpython/hmac.py.exp new file mode 100644 index 00000000000..4b646ba5d39 --- /dev/null +++ b/tests/circuitpython/hmac.py.exp @@ -0,0 +1,19 @@ +b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7 +b617318655057264e28bc0b6fb378c8ef146be00 +5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 +9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2 +d62ad25bb128e96ab6ef43464aaf2bb91b5b85f013381e9ba6be747e8d0911b2 +5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74832607d0 +True +True +RuntimeError +NotImplementedError +True +True +32 64 hmac-sha256 +20 64 hmac-sha1 +ValueError +True +False +False +True diff --git a/tests/cmdline/cmd_showbc.py.exp b/tests/cmdline/cmd_showbc.py.exp index 8ac408c16a6..b839de6d1ed 100644 --- a/tests/cmdline/cmd_showbc.py.exp +++ b/tests/cmdline/cmd_showbc.py.exp @@ -185,7 +185,7 @@ arg names: 58 UNARY_OP 1 __neg__ 59 STORE_FAST 9 60 LOAD_FAST 0 -61 UNARY_OP 3 +61 UNARY_OP 3 \$ 62 STORE_FAST 10 63 LOAD_FAST 0 64 LOAD_DEREF 14 @@ -206,7 +206,7 @@ arg names: 84 LOAD_DEREF 14 86 LOAD_FAST 1 87 BINARY_OP 2 __eq__ -88 UNARY_OP 3 +88 UNARY_OP 3 \$ 89 STORE_FAST 10 90 LOAD_DEREF 14 92 LOAD_ATTR c diff --git a/tests/cmdline/cmd_showbc_const.py.exp b/tests/cmdline/cmd_showbc_const.py.exp index a8be765c822..6faeb941d7d 100644 --- a/tests/cmdline/cmd_showbc_const.py.exp +++ b/tests/cmdline/cmd_showbc_const.py.exp @@ -76,7 +76,7 @@ arg names: 34 RAISE_OBJ 35 DUP_TOP 36 LOAD_NAME AttributeError -38 BINARY_OP 8 +38 BINARY_OP 8 \$ 39 POP_JUMP_IF_FALSE 44 41 POP_TOP 42 POP_EXCEPT_JUMP 45 diff --git a/tests/cmdline/repl_autocomplete_underscore.py b/tests/cmdline/repl_autocomplete_underscore.py index e685a7fe7ff..a0ad4aadf0f 100644 --- a/tests/cmdline/repl_autocomplete_underscore.py +++ b/tests/cmdline/repl_autocomplete_underscore.py @@ -21,7 +21,6 @@ def public_property(self): @property def _private_property(self): return 99 - {\x04} # Paste executed diff --git a/tests/cmdline/repl_autocomplete_underscore.py.exp b/tests/cmdline/repl_autocomplete_underscore.py.exp index 98e6c2aeb05..9c38a6b5900 100644 --- a/tests/cmdline/repl_autocomplete_underscore.py.exp +++ b/tests/cmdline/repl_autocomplete_underscore.py.exp @@ -26,7 +26,7 @@ paste mode; Ctrl-C to cancel, Ctrl-D to finish === def _private_property(self): === return 99 === \$ -=== \$ +>>> \$ >>> # Paste executed >>> \$ >>> # Create an instance diff --git a/tests/cmdline/repl_ctrl_c_interrupt_execution.py b/tests/cmdline/repl_ctrl_c_interrupt_execution.py new file mode 100644 index 00000000000..b125e2c16b4 --- /dev/null +++ b/tests/cmdline/repl_ctrl_c_interrupt_execution.py @@ -0,0 +1,8 @@ +# sigint: deliver via controlling terminal +# Test that Ctrl-C (SIGINT) interrupts blocking code execution. +# MicroPython restores original terminal mode (ISIG on) during +# execution, so the PTY terminal driver generates SIGINT from \x03. +import time +time.sleep(10) +{\x03} +print('repl still responds') diff --git a/tests/cmdline/repl_ctrl_c_interrupt_execution.py.exp b/tests/cmdline/repl_ctrl_c_interrupt_execution.py.exp new file mode 100644 index 00000000000..89c34b130be --- /dev/null +++ b/tests/cmdline/repl_ctrl_c_interrupt_execution.py.exp @@ -0,0 +1,15 @@ + +Adafruit CircuitPython \.\+ version +>>> # sigint: deliver via controlling terminal +>>> # Test that Ctrl-C (SIGINT) interrupts blocking code execution. +>>> # MicroPython restores original terminal mode (ISIG on) during +>>> # execution, so the PTY terminal driver generates SIGINT from \x03. +>>> import time +>>> time.sleep(10) +######## +\.\*Traceback (most recent call last): + File "", line 1, in +KeyboardInterrupt: \$ +>>> print('repl still responds') +repl still responds +>>> \$ diff --git a/tests/cmdline/repl_paste.py b/tests/cmdline/repl_paste.py index 7cec450fce7..8c4f341701f 100644 --- a/tests/cmdline/repl_paste.py +++ b/tests/cmdline/repl_paste.py @@ -26,10 +26,10 @@ def calculate(n): {\x05} def function_with_blanks(): print('First line') - +{\x20}{\x20}{\x20}{\x20} print('After blank line') - - +{\x20}{\x20}{\x20}{\x20} +{\x20}{\x20}{\x20}{\x20} print('After two blank lines') function_with_blanks() @@ -40,10 +40,10 @@ def function_with_blanks(): class TestClass: def __init__(self, value): self.value = value - +{\x20}{\x20}{\x20}{\x20} def display(self): print(f'Value is: {self.value}') - +{\x20}{\x20}{\x20}{\x20} def double(self): self.value *= 2 return self.value @@ -82,7 +82,7 @@ def bad_syntax(: {\x05} def will_error(): undefined_variable - +{\x20}{\x20}{\x20}{\x20} will_error() {\x04} diff --git a/tests/cmdline/repl_paste.py.exp b/tests/cmdline/repl_paste.py.exp index cc5ac2f0800..c60f07fe8d4 100644 --- a/tests/cmdline/repl_paste.py.exp +++ b/tests/cmdline/repl_paste.py.exp @@ -12,6 +12,7 @@ paste mode; Ctrl-C to cancel, Ctrl-D to finish === \$ Hello from paste mode! >>> \$ +>>> \$ >>> # Paste mode with multiple indentation levels >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -34,6 +35,7 @@ Even: 2 Odd: 3 Even: 4 >>> \$ +>>> \$ >>> # Paste mode with blank lines >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -52,6 +54,7 @@ First line After blank line After two blank lines >>> \$ +>>> \$ >>> # Paste mode with class definition and multiple methods >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -76,6 +79,7 @@ Value is: 21 Doubled: 42 Value is: 42 >>> \$ +>>> \$ >>> # Paste mode with exception handling >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -90,6 +94,7 @@ paste mode; Ctrl-C to cancel, Ctrl-D to finish Caught division by zero Finally block executed >>> \$ +>>> \$ >>> # Cancel paste mode with Ctrl-C >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -113,6 +118,7 @@ Traceback (most recent call last): File "", line 2 SyntaxError: invalid syntax >>> \$ +>>> \$ >>> # Paste mode with runtime error >>> \$ paste mode; Ctrl-C to cancel, Ctrl-D to finish @@ -127,6 +133,7 @@ Traceback (most recent call last): File "", line 3, in will_error NameError: name 'undefined_variable' isn't defined >>> \$ +>>> \$ >>> # Final test to show REPL is still functioning >>> 1 + 2 + 3 6 diff --git a/tests/cpydiff/core_class_dir.py b/tests/cpydiff/core_class_dir.py new file mode 100644 index 00000000000..e2b0e877cdc --- /dev/null +++ b/tests/cpydiff/core_class_dir.py @@ -0,0 +1,14 @@ +""" +categories: Core,Classes +description: dir() does not convert __dir__ return value to a sorted list +cause: MicroPython's dir() returns the value from __dir__ as-is, without iterating it into a list or sorting it. +workaround: Have __dir__ return a sorted list directly. +""" + + +class C: + def __dir__(self): + return "cba" + + +print(dir(C())) diff --git a/tests/cpydiff/core_class_strrettype.py b/tests/cpydiff/core_class_strrettype.py new file mode 100644 index 00000000000..398b5bb1a3b --- /dev/null +++ b/tests/cpydiff/core_class_strrettype.py @@ -0,0 +1,14 @@ +""" +categories: Core,Classes +description: ``__str__`` returning non-string type does not raise TypeError +cause: MicroPython's instance_print does not validate that ``__str__`` or ``__repr__`` return a str or its subclass +workaround: Ensure ``__str__`` and ``__repr__`` always return a str instance or its subclass +""" + + +class Foo: + def __str__(self): + return True + + +print(str(Foo())) diff --git a/tests/cpydiff/core_class_subclassret.py b/tests/cpydiff/core_class_subclassret.py new file mode 100644 index 00000000000..681a16e879b --- /dev/null +++ b/tests/cpydiff/core_class_subclassret.py @@ -0,0 +1,19 @@ +""" +categories: Core,Classes +description: str() does not preserve str subclass type from ``__str__`` or ``__repr__`` return value +cause: Implementation discards str subclass type returned by ``__str__`` or ``__repr__`` and always returns a plain str instance from str(). +workaround: Do not rely on str() preserving str subclass types +""" + + +class MyStr(str): + pass + + +class Foo: + def __str__(self): + return MyStr("abc") + + +result = str(Foo()) +print(type(result)) diff --git a/tests/cpydiff/core_function_star.py b/tests/cpydiff/core_function_star.py new file mode 100644 index 00000000000..26f5effd4e3 --- /dev/null +++ b/tests/cpydiff/core_function_star.py @@ -0,0 +1,16 @@ +""" +categories: Core,Functions +description: ``*args`` cannot follow a keyword argument +cause: MicroPython is optimised for code space. For more information see `this issue `_. +workaround: Re-order the arguments +""" + + +def f(x, y): + return x + y + + +try: + print(f(y=1, *(3,))) +except Exception as e: + print(e) diff --git a/tests/cpydiff/syntax_annotation_expression.py b/tests/cpydiff/syntax_annotation_expression.py new file mode 100644 index 00000000000..91814ec9522 --- /dev/null +++ b/tests/cpydiff/syntax_annotation_expression.py @@ -0,0 +1,24 @@ +""" +categories: Syntax,Annotations +description: MicroPython accepts type annotations on expressions where CPython forbids them. +cause: To reduce code size, MicroPython does not check the form of expressions with type annotations applied. +workaround: Always check for valid Python code using a linting tool. + +The expressions themselves are not evaluated. +""" + + +def test(expr): + code = f"def f():\n {expr}: int" + print(code) + try: + exec(code) + print("OK") + except SyntaxError as e: + print("SyntaxError") + print() + + +test("print('test')") +test("[x,y]") +test("x,y") diff --git a/tests/cpydiff/types_bytes_decode_encoding.py b/tests/cpydiff/types_bytes_decode_encoding.py new file mode 100644 index 00000000000..17a129a2af9 --- /dev/null +++ b/tests/cpydiff/types_bytes_decode_encoding.py @@ -0,0 +1,30 @@ +""" +categories: Types,bytes +description: bytes.decode() only supports encoding arguments 'utf8', 'utf-8' and 'ascii'. Other encodings like 'latin-1' are not supported. Other string forms such as 'UTF8' are not supported. +cause: MicroPython is optimised for embedded systems and only includes UTF-8 and ASCII codec support with simple matching to save memory and code size. `The same restriction applies to str constructor `. See also `unicode_support`. +workaround: Convert data to UTF-8 before processing, or implement custom encoding/decoding if needed. Ensure encoding argument is one of the accepted forms. +""" + +# Both CPython and MicroPython support utf8 and ascii encodings +print(b"caf\xc3\xa9".decode("utf8")) # codespell:ignore caf +print(b"cafe".decode("ascii")) + +# MicroPython does not support additional encodings +try: + b"\xe9".decode("latin-1") # 'é' in latin-1 + print("latin-1 supported") +except (ValueError, NotImplementedError, LookupError) as e: + print("latin-1 not supported:", type(e).__name__) + +try: + b"\x80".decode("cp1252") # Euro sign in cp1252 + print("cp1252 supported") +except (ValueError, NotImplementedError, LookupError) as e: + print("cp1252 not supported:", type(e).__name__) + +# Encoding arguments must match exactly in MicroPython +try: + b"hello".decode("ASCII") + print("Capital letters ASCII supported") +except (ValueError, NotImplementedError, LookupError) as e: + print("Capital letters ASCII not supported:", type(e).__name__) diff --git a/tests/cpydiff/types_bytes_decode_kwargs.py b/tests/cpydiff/types_bytes_decode_kwargs.py new file mode 100644 index 00000000000..a1ca2b74c5c --- /dev/null +++ b/tests/cpydiff/types_bytes_decode_kwargs.py @@ -0,0 +1,19 @@ +""" +categories: Types,bytes +description: bytes.decode() does not accept keyword arguments, only positional arguments +cause: MicroPython optimizes for code size and does not implement keyword argument handling for bytes.decode() +workaround: Use positional arguments instead of keyword arguments +""" + +# CPython accepts keyword arguments, MicroPython only accepts positional +b = b"hello\xffworld" + +try: + # Using keyword arguments + result = b.decode(encoding="utf-8", errors="ignore") + print("kwargs supported:", repr(result)) +except TypeError as e: + print("kwargs not supported: TypeError") + # Workaround: use positional arguments + result = b.decode("utf-8", "ignore") + print("positional args work:", repr(result)) diff --git a/tests/cpydiff/types_bytes_encoding.py b/tests/cpydiff/types_bytes_encoding.py new file mode 100644 index 00000000000..0fe3c976589 --- /dev/null +++ b/tests/cpydiff/types_bytes_encoding.py @@ -0,0 +1,21 @@ +""" +categories: Types,bytes +description: bytes() constructor only supports encoding arguments 'utf8', 'utf-8' and 'ascii'. Other encodings like 'latin-1' are not supported. Other string forms such as 'UTF8' are not supported. +cause: MicroPython is optimised for embedded systems and has limited codec support to save code size. `A similar restriction applies to str.encode() `. See also `unicode_support`. +workaround: Implement other encoding conversions manually by parsing the result of str.encode() or bytes() constructor. +""" + +# Both CPython and MicroPython can encode this emoji as UTF-8 bytes +print(bytes("😀", "utf8")) + +# Both CPython and MicroPython will fail to encode this emoji as ASCII bytes +try: + print(bytes("😀", "ascii")) +except UnicodeError: + print("UnicodeError") + +# Other encodings or string formats aren't accepted by MicroPython +try: + print(bytes("😀", "UTF-8")) +except LookupError: + print("LookupError") diff --git a/tests/cpydiff/types_int_to_bytes.py b/tests/cpydiff/types_int_to_bytes.py deleted file mode 100644 index 6530a2a32ec..00000000000 --- a/tests/cpydiff/types_int_to_bytes.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -categories: Types,int -description: ``to_bytes`` method doesn't implement signed parameter. -cause: The ``signed`` keyword-only parameter is not implemented for ``int.to_bytes()``. - -When the integer is negative, MicroPython behaves the same as CPython ``int.to_bytes(..., signed=True)`` - -When the integer is non-negative, MicroPython behaves the same as CPython ``int.to_bytes(..., signed=False)``. - -(The difference is subtle, but in CPython a positive integer converted with ``signed=True`` may require one byte more in the output length, in order to fit the 0 sign bit.) - -workaround: Take care when calling ``to_bytes()`` on an integer value which may be negative. -""" - -x = -1 -print(x.to_bytes(1, "big")) diff --git a/tests/cpydiff/types_range_limits.py b/tests/cpydiff/types_range_limits.py index e53d5fd4088..dca830d3fbc 100644 --- a/tests/cpydiff/types_range_limits.py +++ b/tests/cpydiff/types_range_limits.py @@ -1,7 +1,7 @@ """ categories: Types,range -description: Range objects with large start or stop arguments misbehave. -cause: Intermediate calculations overflow the C mp_int_t type +description: Range arguments must fit in a machine word; large start or stop values misbehave. +cause: Range stores its arguments as the C mp_int_t type, and intermediate calculations also use it. workaround: Avoid using such ranges """ @@ -13,6 +13,12 @@ except OverflowError: print("OverflowError") +# A range with start or stop outside [-maxsize, maxsize] cannot be created, even if the range itself would be small. +try: + print(range(maxsize + 1, maxsize + 2)) +except OverflowError: + print("OverflowError") + # A range with `stop-start` exceeding sys.maxsize has incorrect len(), while CPython cannot calculate len(). try: print(len(range(-maxsize, maxsize))) diff --git a/tests/cpydiff/types_str_encode_encoding.py b/tests/cpydiff/types_str_encode_encoding.py new file mode 100644 index 00000000000..12f6b947262 --- /dev/null +++ b/tests/cpydiff/types_str_encode_encoding.py @@ -0,0 +1,21 @@ +""" +categories: Types,str +description: str.encode() constructor only supports encoding arguments 'utf8', 'utf-8' and 'ascii'. Other encodings like 'latin-1' are not supported. Other string forms such as 'UTF8' are not supported. +cause: MicroPython is optimised for embedded systems and has limited codec support to save code size. `A similar restriction applies to bytes constructor `. See also `unicode_support`. +workaround: Implement encoding conversions manually by parsing the result of str.encode() or bytes() constructor. +""" + +# Both CPython and MicroPython can encode this emoji as UTF-8 bytes +print("😀".encode("utf8")) + +# Both CPython and MicroPython will fail to encode this emoji as ASCII bytes +try: + print("😀".encode("ascii")) +except UnicodeError: + print("UnicodeError") + +# Other encodings or string formats aren't accepted by MicroPython: +try: + print("😀".encode("UTF-8")) +except LookupError: + print("LookupError") diff --git a/tests/cpydiff/types_str_encode_errors.py b/tests/cpydiff/types_str_encode_errors.py new file mode 100644 index 00000000000..2e2550b0538 --- /dev/null +++ b/tests/cpydiff/types_str_encode_errors.py @@ -0,0 +1,19 @@ +""" +categories: Types,str +description: str.encode() and bytes() constructor ignore any ``errors`` argument specified. If the encoding is specified as ``'ascii'`` and a non-ASCII byte is found in the string then an exception is always raised. +cause: MicroPython is optimised for embedded systems and has limited codec support to save code size. See also `unicode_support`. +workaround: Handle encoding errors manually by parsing the result of str.encode() or bytes() constructor. +""" + +# CPython will replace the emoji with an ASCII '?' but MicroPython will +# raise an exception +try: + print("😀".encode("ascii", "replace")) +except UnicodeError: + print("UnicodeError") + +# CPython will ignore the emoji in the result but MicroPython will raise an exception +try: + print("😀".encode("ascii", "ignore")) +except UnicodeError: + print("UnicodeError") diff --git a/tests/cpydiff/types_str_encoding.py b/tests/cpydiff/types_str_encoding.py new file mode 100644 index 00000000000..b97b984c22e --- /dev/null +++ b/tests/cpydiff/types_str_encoding.py @@ -0,0 +1,30 @@ +""" +categories: Types,str +description: str() constructor only supports encoding arguments 'utf8', 'utf-8' and 'ascii'. Other encodings like 'latin-1' are not supported. Other string forms such as 'UTF8' are not supported. +cause: MicroPython is optimised for embedded systems and only includes UTF-8 and ASCII codec support with simple matching to save memory and code size. `The same restriction applies to bytes.decode() `. See also `unicode_support`. +workaround: Convert data to UTF-8 before processing, or implement custom encoding/decoding if needed. Ensure encoding argument is one of the accepted forms. +""" + +# Both CPython and MicroPython support utf8 and ascii encodings +print(str(b"caf\xc3\xa9", "utf8")) # codespell:ignore caf +print(str(b"cafe", "ascii")) + +# MicroPython does not support additional encodings +try: + str(b"\xe9", "latin-1") # 'é' in latin-1 + print("latin-1 supported") +except (ValueError, NotImplementedError, LookupError) as e: + print("latin-1 not supported:", type(e).__name__) + +try: + str(b"\x80", "cp1252") # Euro sign in cp1252 + print("cp1252 supported") +except (ValueError, NotImplementedError, LookupError) as e: + print("cp1252 not supported:", type(e).__name__) + +# Encoding arguments must match exactly in MicroPython +try: + str(b"hello", "ASCII") + print("Capital letters ASCII supported") +except (ValueError, NotImplementedError, LookupError) as e: + print("Capital letters ASCII not supported:", type(e).__name__) diff --git a/tests/cpydiff/types_str_repr_nonprintable.py b/tests/cpydiff/types_str_repr_nonprintable.py new file mode 100644 index 00000000000..6f9af65d8e1 --- /dev/null +++ b/tests/cpydiff/types_str_repr_nonprintable.py @@ -0,0 +1,11 @@ +""" +categories: Types,str +description: repr() may print some non-printable Unicode characters literally instead of as escape sequences +cause: MicroPython uses a simplified heuristic to determine printable characters, avoiding the need for a full Unicode character database (saves memory). It prints characters >= U+0080 (excluding surrogates) as UTF-8. CPython uses the Unicode database to identify non-printable characters like noncharacters (U+FFFx in each plane). +workaround: Accept the difference for embedded use cases, or use ascii() or manual escaping if exact control is needed. +""" + +# These are noncharacters that CPython escapes but MicroPython prints +# showing as hex to avoid display issues in documentation tables +print("U+FFFF:", repr("\uffff").encode("utf-8").hex()) +print("U+1FFFF:", repr("\U0001ffff").encode("utf-8").hex()) diff --git a/tests/extmod/asyncio_cancel_self.py b/tests/extmod/asyncio_cancel_self.py index a437edb5403..3ed3ee88214 100644 --- a/tests/extmod/asyncio_cancel_self.py +++ b/tests/extmod/asyncio_cancel_self.py @@ -25,4 +25,4 @@ async def main(): try: asyncio.run(main()) except RuntimeError as er: - print(er) + print(str(er) or "can't cancel self") diff --git a/tests/extmod/machine_hard_timer.py b/tests/extmod/machine_hard_timer.py deleted file mode 100644 index 8fe42ea8508..00000000000 --- a/tests/extmod/machine_hard_timer.py +++ /dev/null @@ -1,45 +0,0 @@ -import sys - -try: - from machine import Timer - from time import sleep_ms -except: - print("SKIP") - raise SystemExit - -if sys.platform == "esp8266": - timer = Timer(0) -else: - # Hardware timers are not implemented. - print("SKIP") - raise SystemExit - -# Test both hard and soft IRQ handlers and both one-shot and periodic -# timers. We adjust period in tests/extmod/machine_soft_timer.py, so try -# adjusting freq here instead. The heap should be locked in hard callbacks -# and unlocked in soft callbacks. - - -def callback(t): - print("callback", mode[1], kind[1], freq, end=" ") - try: - allocate = bytearray(1) - print("unlocked") - except MemoryError: - print("locked") - - -modes = [(Timer.ONE_SHOT, "one-shot"), (Timer.PERIODIC, "periodic")] -kinds = [(False, "soft"), (True, "hard")] - -for mode in modes: - for kind in kinds: - for freq in 50, 25: - timer.init( - mode=mode[0], - freq=freq, - hard=kind[0], - callback=callback, - ) - sleep_ms(90) - timer.deinit() diff --git a/tests/extmod/machine_hard_timer.py.exp b/tests/extmod/machine_hard_timer.py.exp deleted file mode 100644 index 26cdc644fdd..00000000000 --- a/tests/extmod/machine_hard_timer.py.exp +++ /dev/null @@ -1,16 +0,0 @@ -callback one-shot soft 50 unlocked -callback one-shot soft 25 unlocked -callback one-shot hard 50 locked -callback one-shot hard 25 locked -callback periodic soft 50 unlocked -callback periodic soft 50 unlocked -callback periodic soft 50 unlocked -callback periodic soft 50 unlocked -callback periodic soft 25 unlocked -callback periodic soft 25 unlocked -callback periodic hard 50 locked -callback periodic hard 50 locked -callback periodic hard 50 locked -callback periodic hard 50 locked -callback periodic hard 25 locked -callback periodic hard 25 locked diff --git a/tests/extmod/machine_mem_backup.py b/tests/extmod/machine_mem_backup.py new file mode 100644 index 00000000000..db2ff910a73 --- /dev/null +++ b/tests/extmod/machine_mem_backup.py @@ -0,0 +1,58 @@ +# Test machine.mem_backup() function. + +try: + import machine + + mem = machine.mem_backup() +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +# Discovery: -1 returns a tuple of all regions. +regions = machine.mem_backup(-1) +print("regions is tuple:", isinstance(regions, tuple)) +print("at least one region:", len(regions) >= 1) +print("all memoryviews:", all(isinstance(r, memoryview) for r in regions)) +print("all len > 0:", all(len(r) > 0 for r in regions)) +print("all valid itemsize:", all(r.itemsize in (1, 4) for r in regions)) + +# Default region (index 0). +print("memoryview:", isinstance(mem, memoryview)) +print("len > 0:", len(mem) > 0) +print("itemsize ok:", mem.itemsize in (1, 4)) + +# Small-value write/read. +mem[0] = 42 +print(mem[0] == 42) + +if mem.itemsize == 4: + # Values >= 0x40000000 are big-ints on 32-bit targets; check that + # word-only backup registers receive them as aligned word writes. + for val in (0x40000000, 0x41020304, 0x7FFFFFFF, 0xDEADBEEF): + mem[0] = val + print(mem[0] == val) + mem[0] = -1 + print(mem[0] == 0xFFFFFFFF) +else: + for val in (0x80, 0xA5, 0xFF, 0xC3): + mem[0] = val + print(mem[0] == val) + mem[0] = 0 + print(mem[0] == 0) + +last = len(mem) - 1 +mem[last] = 1 +print(mem[last] == 1) + +# Out-of-range region raises ValueError. +try: + machine.mem_backup(len(regions)) + print("no error") +except ValueError: + print("ValueError") + +try: + machine.mem_backup(-2) + print("no error") +except ValueError: + print("ValueError") diff --git a/tests/extmod/machine_mem_backup.py.exp b/tests/extmod/machine_mem_backup.py.exp new file mode 100644 index 00000000000..c3d858c86d6 --- /dev/null +++ b/tests/extmod/machine_mem_backup.py.exp @@ -0,0 +1,17 @@ +regions is tuple: True +at least one region: True +all memoryviews: True +all len > 0: True +all valid itemsize: True +memoryview: True +len > 0: True +itemsize ok: True +True +True +True +True +True +True +True +ValueError +ValueError diff --git a/tests/extmod/machine_timer.py b/tests/extmod/machine_timer.py index ef97ea4e949..cb5f3fa67e2 100644 --- a/tests/extmod/machine_timer.py +++ b/tests/extmod/machine_timer.py @@ -1,5 +1,3 @@ -import sys - try: from machine import Timer from time import sleep_ms @@ -7,42 +5,145 @@ print("SKIP") raise SystemExit -if sys.platform in ("esp32", "esp8266", "nrf"): - # Software timers aren't implemented on the esp32 and esp8266 ports. - # The nrf port doesn't support selection of hard and soft callbacks, - # and only allows Timer(period=N), not Timer(freq=N). +import sys + +if sys.platform == "nrf": + # Note: The nrf port supports machine.Timer, but is not compatible: It lacks + # the .init() method and freq argument, the period argument is microseconds + # instead milliseconds, and the ONE_SHOT constant is named ONESHOT. print("SKIP") raise SystemExit -else: - timer_id = -1 - -# Test both hard and soft IRQ handlers and both one-shot and periodic -# timers. We adjust period in tests/extmod/machine_soft_timer.py, so try -# adjusting freq here instead. The heap should be locked in hard callbacks -# and unlocked in soft callbacks. - - -def callback(t): - print("callback", mode[1], kind[1], freq, end=" ") - try: - allocate = bytearray(1) - print("unlocked") - except MemoryError: - print("locked") - - -modes = [(Timer.ONE_SHOT, "one-shot"), (Timer.PERIODIC, "periodic")] -kinds = [(False, "soft"), (True, "hard")] - -for mode in modes: - for kind in kinds: - for freq in 50, 25: - timer = Timer( - timer_id, - mode=mode[0], - freq=freq, - hard=kind[0], - callback=callback, + +import unittest + +# Hardware timers are only supported on the esp32 port +NUM_HARDWARE_TIMERS = 0 +if sys.platform == "esp32": + if "ESP32-C2" in sys.implementation._machine: + # Only one hardware timer on ESP32-C2 + NUM_HARDWARE_TIMERS = 1 + else: + # at least two on other ESP32 SoCs + NUM_HARDWARE_TIMERS = 2 + +# Hard IRQs are not supported on the esp32 port +SUPPORTS_HARD_IRQ = sys.platform != "esp32" + + +class Test(unittest.TestCase): + def test_virtual_create(self): + self._test_create(-1) + self._test_create_multiple(-1, -1) + + @unittest.skipUnless(NUM_HARDWARE_TIMERS > 0, "no hardware timers") + def test_hardware_create(self): + self._test_create(0) + + @unittest.skipUnless(NUM_HARDWARE_TIMERS >= 2, "less than 2 hardware timers") + def test_hardware_create_multiple(self): + self._test_create_multiple(0, 1) + + def test_virtual_softirq(self): + self._test_all_freq_period(-1, Timer.ONE_SHOT, False) + self._test_all_freq_period(-1, Timer.PERIODIC, False) + + @unittest.skipUnless(SUPPORTS_HARD_IRQ, "no hard-irq support") + def test_virtual_hardirq(self): + self._test_all_freq_period(-1, Timer.ONE_SHOT, True) + self._test_all_freq_period(-1, Timer.PERIODIC, True) + + @unittest.skipUnless(NUM_HARDWARE_TIMERS > 0, "no hardware timers") + def test_hardware_softirq(self): + self._test_all_freq_period(0, Timer.ONE_SHOT, False) + self._test_all_freq_period(0, Timer.PERIODIC, False) + + @unittest.skipUnless(NUM_HARDWARE_TIMERS > 0, "no hardware timers") + @unittest.skipUnless(SUPPORTS_HARD_IRQ, "no hard-irq support") + def test_hardware_hardirq(self): + self._test_all_freq_period(0, Timer.ONE_SHOT, True) + self._test_all_freq_period(0, Timer.PERIODIC, True) + + def _test_create(self, id): + # create and deinit + t = Timer(id) + t.init(freq=1) + t.deinit() + + # deinit again + t.deinit() + + # init a large number of times to catch bugs like + # https://github.com/micropython/micropython/issues/19162 + for _ in range(256): + t.init(freq=1) + t.deinit() + + def _test_create_multiple(self, *ids): + # create and deinit + timers = [] + for id in ids: + t = Timer(id) + self.assertFalse(t in timers) + t.init(freq=1) + timers.append(t) + for t in timers: + t.deinit() + + # create and deinit in reverse order + timers = [] + for id in ids: + t = Timer(id) + self.assertFalse(t in timers) + t.init(freq=1) + timers.append(t) + for t in reversed(timers): + t.deinit() + + def _test_all_freq_period(self, id, mode, hard): + # test two different freq and period arguments + for period, freq_period_arg in ( + (1000 // 50, {"freq": 50}), + (1000 // 25, {"freq": 25}), + (20, {"period": 20}), + (40, {"period": 40}), + ): + callback_results = [] + + def _callback(_): + try: + allocate = bytearray(1) + locked = False + except MemoryError: + locked = True + callback_results.append(locked) + + t = Timer(id) + t.init( + mode=mode, + callback=_callback, + hard=hard, + **freq_period_arg, ) - sleep_ms(90) - timer.deinit() + + # Note: These sleep durations are such that there is always at least + # 10ms between when the callback fires and we perform our checks, + # thus this test also implicitly checks that timers and/or sleep_ms + # do not drift more than 10ms. + total = 0 + for duration in (0, 10, 20, 40, 20): + sleep_ms(duration) + total += duration + + # number of times the callback should have been called + n = total // period + if mode == Timer.ONE_SHOT: + n = min(n, 1) + + # callback reports whether memory was locked, which + # should equal whether the timer uses hard IRQs + self.assertEqual([hard] * n, callback_results) + t.deinit() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/extmod/machine_timer.py.exp b/tests/extmod/machine_timer.py.exp deleted file mode 100644 index 26cdc644fdd..00000000000 --- a/tests/extmod/machine_timer.py.exp +++ /dev/null @@ -1,16 +0,0 @@ -callback one-shot soft 50 unlocked -callback one-shot soft 25 unlocked -callback one-shot hard 50 locked -callback one-shot hard 25 locked -callback periodic soft 50 unlocked -callback periodic soft 50 unlocked -callback periodic soft 50 unlocked -callback periodic soft 50 unlocked -callback periodic soft 25 unlocked -callback periodic soft 25 unlocked -callback periodic hard 50 locked -callback periodic hard 50 locked -callback periodic hard 50 locked -callback periodic hard 50 locked -callback periodic hard 25 locked -callback periodic hard 25 locked diff --git a/tests/extmod/random_extra.py b/tests/extmod/random_extra.py index aa05053377b..958fb8f5f3f 100644 --- a/tests/extmod/random_extra.py +++ b/tests/extmod/random_extra.py @@ -48,6 +48,11 @@ assert 2 <= random.randint(2, 6) <= 6 assert -2 <= random.randint(-2, 2) <= 2 +# CIRCUITPY-CHANGE: upper bound at the largest 32-bit mp_int_t must not overflow +for i in range(50): + assert 0 <= random.randint(0, 0x7FFFFFFF) <= 0x7FFFFFFF + assert 1 <= random.randint(1, 0x7FFFFFFF) <= 0x7FFFFFFF + # empty range try: random.randint(2, 1) diff --git a/tests/extmod/re_stack_overflow2.py b/tests/extmod/re_stack_overflow2.py new file mode 100644 index 00000000000..c5ddd012b32 --- /dev/null +++ b/tests/extmod/re_stack_overflow2.py @@ -0,0 +1,25 @@ +# Test overflow in re.compile output code. + +try: + import re +except ImportError: + print("SKIP") + raise SystemExit + + +def test_re(r): + try: + re.compile(r) + except: + print("Error") + + +try: + r = "(" * 65536 + ")" * 65536 +except MemoryError: + print("SKIP") + raise SystemExit + +# This happens to trigger RecursionError on current versions of CPython +# (tested with 3.13.5) as well, so no .exp file is needed. +test_re(r) diff --git a/tests/extmod/select_poll_eintr.py.exp b/tests/extmod/select_poll_eintr.py.exp new file mode 100644 index 00000000000..2b15f86649c --- /dev/null +++ b/tests/extmod/select_poll_eintr.py.exp @@ -0,0 +1,5 @@ +poll +thread gc start +thread gc end +result: [] +dt in range diff --git a/tests/extmod/socket_badconstructor.py b/tests/extmod/socket_badconstructor.py index 1ea5d750b3e..25d981f70a7 100644 --- a/tests/extmod/socket_badconstructor.py +++ b/tests/extmod/socket_badconstructor.py @@ -16,10 +16,12 @@ except TypeError: print("TypeError") +# This may or may not raise an exception, depending on the socket implementation. +# The test is here for coverage. try: s = socket.socket(socket.AF_INET, 123456) except OSError: - print("OSError") + pass try: s = socket.socket(socket.AF_INET, socket.SOCK_RAW, None) diff --git a/tests/extmod/tls_psk.py b/tests/extmod/tls_psk.py new file mode 100644 index 00000000000..cc49ecfc8ce --- /dev/null +++ b/tests/extmod/tls_psk.py @@ -0,0 +1,42 @@ +# Test the tls.SSLContext PSK attributes (psk_identity, psk_key, +# server_psk_keys) and basic validation, without needing a network connection. + +try: + import tls + + # PSK support is optional; psk_identity only exists when it's enabled. + tls.SSLContext(tls.PROTOCOL_TLS_CLIENT).psk_identity +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +ctx = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT) + +# The PSK attributes default to None. +print(ctx.psk_identity, ctx.psk_key, ctx.server_psk_keys) + +# They can be set and read back. +ctx.psk_identity = b"micropython" +ctx.psk_key = b"secret-shared-key" +ctx.server_psk_keys = {b"micropython": b"secret-shared-key"} +print(ctx.psk_identity, ctx.psk_key, ctx.server_psk_keys) + +# A PSK key longer than mbedTLS's maximum (MBEDTLS_PSK_MAX_LEN) is rejected +# when it is set on the context (the identity is already set above). +try: + ctx.psk_key = b"x" * 49 + print("no error") +except OSError: + print("OSError") + +# The key/identity are only applied once both are set, in either order. Here +# the over-length key is set first (a no-op while the identity is None), so the +# rejection only surfaces when the identity is set and the pair is applied. +ctx = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT) +ctx.psk_key = b"x" * 49 +try: + ctx.psk_identity = b"micropython" + print("no error") +except OSError: + print("OSError") diff --git a/tests/extmod/tls_psk.py.exp b/tests/extmod/tls_psk.py.exp new file mode 100644 index 00000000000..060d5120d85 --- /dev/null +++ b/tests/extmod/tls_psk.py.exp @@ -0,0 +1,4 @@ +None None None +b'micropython' b'secret-shared-key' {b'micropython': b'secret-shared-key'} +OSError +OSError diff --git a/tests/extmod/uctypes_array_load_store.py b/tests/extmod/uctypes_array_load_store.py index 695352da579..bea1ffc4a74 100644 --- a/tests/extmod/uctypes_array_load_store.py +++ b/tests/extmod/uctypes_array_load_store.py @@ -1,4 +1,5 @@ # Test uctypes array, load and store, with array size > 1 +import sys try: import uctypes @@ -13,18 +14,82 @@ print("SKIP") raise SystemExit +try: + import unittest +except MemoryError: + print("SKIP-TOO-LARGE") # some small boards can't fit unittest in RAM + raise SystemExit + +# MicroPython V2.0 will enforce bounds on bytearray setters, V1.x truncates +# CIRCUITPY-CHANGE: CircuitPython always enforces these bounds (OVERFLOW_CHECKS in py/binary.c). +is_v2 = hasattr(sys.implementation, "_v2") or sys.implementation.name == "circuitpython" + N = 5 +PLACEHOLDER = 99 + + +class Test(unittest.TestCase): + def test_native_endian(self): + self._test_endian("NATIVE") + + def test_little_endian(self): + self._test_endian("LITTLE_ENDIAN") + + def test_big_endian(self): + self._test_endian("BIG_ENDIAN") -for endian in ("NATIVE", "LITTLE_ENDIAN", "BIG_ENDIAN"): - for type_ in ("INT8", "UINT8", "INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64"): - desc = {"arr": (uctypes.ARRAY | 0, getattr(uctypes, type_) | N)} + def _test_endian(self, endian): + for item_type in ( + "INT8", + "UINT8", + "INT16", + "UINT16", + "INT32", + "UINT32", + "INT64", + "UINT64", + ): + print(endian, item_type) + self._test_endian_type(endian, item_type) + + def _test_endian_type(self, endian, item_type): + print("Testing array of", item_type, "with", endian, "endianness") + desc = {"arr": (uctypes.ARRAY | 0, getattr(uctypes, item_type) | N)} + print(repr(desc)) sz = uctypes.sizeof(desc) data = bytearray(sz) + print(sz, repr((uctypes.addressof(data), desc, getattr(uctypes, endian)))) s = uctypes.struct(uctypes.addressof(data), desc, getattr(uctypes, endian)) + is_unsigned = item_type.startswith("U") + item_sz = uctypes.sizeof({"": getattr(uctypes, item_type)}) + + for i in range(N): + n = i - 2 + print(i, n) + # uctypes returns a bytearray for arrays of type UINT8, MicroPython V2 will + # enforce bounds checks on these so we can't assign a negative value + if is_v2 and isinstance(s.arr, bytearray) and n < 0: + print("placeholder value", n) + with self.assertRaises(OverflowError): + s.arr[i] = n + s.arr[i] = PLACEHOLDER + n = PLACEHOLDER + else: + s.arr[i] = n + + print(endian, item_type, sz, *(s.arr[i] for i in range(N))) + for i in range(N): - # CIRCUITPY-CHANGE: overflow checks - try: - s.arr[i] = i - 2 - except OverflowError: - print("OverflowError") - print(endian, type_, sz, *(s.arr[i] for i in range(N))) + n = i - 2 + if is_v2 and isinstance(s.arr, bytearray) and n < 0: + # The code above has swapped in PLACEHOLDER for this value + n = PLACEHOLDER + elif is_unsigned and n < 0: + # other types of unsigned uctypes arrays will "cast" negative values to unsigned + n = n & ((1 << (item_sz * 8)) - 1) + + self.assertEqual(s.arr[i], n) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/extmod/vfs_basic.py b/tests/extmod/vfs_basic.py index 2c0ce8f5295..e88df1a776d 100644 --- a/tests/extmod/vfs_basic.py +++ b/tests/extmod/vfs_basic.py @@ -19,11 +19,11 @@ def umount(self): print(self.id, "umount") def ilistdir(self, dir): - print(self.id, "ilistdir", dir) + print(self.id, "ilistdir", repr(dir)) return iter([("a%d" % self.id, 0, 0)]) def chdir(self, dir): - print(self.id, "chdir", dir) + print(self.id, "chdir", repr(dir)) if self.fail: raise OSError(self.fail) @@ -32,23 +32,23 @@ def getcwd(self): return "dir%d" % self.id def mkdir(self, path): - print(self.id, "mkdir", path) + print(self.id, "mkdir", repr(path)) def remove(self, path): - print(self.id, "remove", path) + print(self.id, "remove", repr(path)) def rename(self, old_path, new_path): - print(self.id, "rename", old_path, new_path) + print(self.id, "rename", repr(old_path), repr(new_path)) def rmdir(self, path): - print(self.id, "rmdir", path) + print(self.id, "rmdir", repr(path)) def stat(self, path): - print(self.id, "stat", path) - return (self.id,) + print(self.id, "stat", repr(path)) + return (self.id, 0, 0, 0, 0, 0, 0, 0, 0, 0) def statvfs(self, path): - print(self.id, "statvfs", path) + print(self.id, "statvfs", repr(path)) return (self.id,) def open(self, file, mode): @@ -64,7 +64,7 @@ def open(self, file, mode): vfs.umount("/" + path) # stat root dir -print(os.stat("/")) +print(tuple(os.stat("/"))) # statvfs root dir; verify that f_namemax has a sensible size print(os.statvfs("/")[9] >= 32) @@ -131,7 +131,7 @@ def open(self, file, mode): os.remove("test_file") os.rename("test_file", "test_file2") os.rmdir("test_dir") -print(os.stat("test_file")) +print(tuple(os.stat("test_file"))) print(os.statvfs("/test_mnt")) open("test_file") open("test_file", "wb") @@ -148,7 +148,7 @@ def open(self, file, mode): # root dir vfs.mount(Filesystem(3), "/") -print(os.stat("/")) +print(tuple(os.stat("/"))) print(os.statvfs("/")) print(os.listdir()) open("test") diff --git a/tests/extmod/vfs_basic.py.exp b/tests/extmod/vfs_basic.py.exp index 536bb4c805d..39a16c444ee 100644 --- a/tests/extmod/vfs_basic.py.exp +++ b/tests/extmod/vfs_basic.py.exp @@ -18,30 +18,30 @@ stat /x OSError ('test_mnt', 16384, 0) StopIteration StopIteration -1 ilistdir / +1 ilistdir '/' ['a1'] -1 ilistdir / +1 ilistdir '/' ['a1'] 2 mount True False ['test_mnt', 'test_mnt2'] -2 ilistdir / +2 ilistdir '/' ['a2'] 3 mount False False OSError OSError OSError -1 chdir / -1 ilistdir +1 chdir '/' +1 ilistdir '' ['a1'] 1 getcwd /test_mntdir1 -1 mkdir test_dir -1 remove test_file -1 rename test_file test_file2 -1 rmdir test_dir -1 stat test_file -(1,) -1 statvfs / +1 mkdir 'test_dir' +1 remove 'test_file' +1 rename 'test_file' 'test_file2' +1 rmdir 'test_dir' +1 stat 'test_file' +(1, 0, 0, 0, 0, 0, 0, 0, 0, 0) +1 statvfs '/' (1,) 1 open test_file r 1 open test_file wb @@ -50,29 +50,29 @@ OSError OSError 3 mount False False (16384, 0, 0, 0, 0, 0, 0, 0, 0, 0) -3 statvfs / +3 statvfs '/' (3,) -3 ilistdir / +3 ilistdir '/' ['a3'] 3 open test r 4 mount False False -3 ilistdir / +3 ilistdir '/' ['mnt', 'a3'] -4 ilistdir / +4 ilistdir '/' ['a4'] -4 chdir / -4 ilistdir +4 chdir '/' +4 ilistdir '' ['a4'] -3 chdir /subdir -3 ilistdir +3 chdir '/subdir' +3 ilistdir '' ['a3'] -3 chdir / +3 chdir '/' 3 umount ['mnt'] 4 umount OSError / 5 mount False False -5 chdir /subdir +5 chdir '/subdir' OSError / diff --git a/tests/extmod/vfs_blockdev_invalid.py b/tests/extmod/vfs_blockdev_invalid.py index 955f8495b3f..3cb97ac3144 100644 --- a/tests/extmod/vfs_blockdev_invalid.py +++ b/tests/extmod/vfs_blockdev_invalid.py @@ -53,6 +53,7 @@ def ioctl(self, op, arg): ERROR_EIO = (OSError, "[Errno 5] EIO") ERROR_EINVAL = (OSError, "[Errno 22] EINVAL") ERROR_TYPE = (TypeError, "can't convert str to int") +ALL_ERROR_TYPES = (OSError, TypeError) def test(vfs_class, test_data): @@ -71,7 +72,7 @@ def test(vfs_class, test_data): try: with fs.open("test", "r") as f: assert error_open is None - except Exception as e: + except ALL_ERROR_TYPES as e: assert error_open is not None assert (type(e), str(e)) == error_open @@ -84,10 +85,27 @@ def test(vfs_class, test_data): assert f.read(1) == "a" assert f.read() == "a" * 63 assert error_read is None - except Exception as e: + except ALL_ERROR_TYPES as e: assert error_read is not None assert (type(e), str(e)) == error_read + # Try mounting this block device + # + # Failing mount operation will return EIO rather than EINVAL, but otherwise + # the result should match error_open + error_mount = ERROR_EIO if error_open == ERROR_EINVAL else error_open + try: + vfs.mount(bdev, "/test_ram") + assert error_mount is None + except ALL_ERROR_TYPES as e: + assert error_mount is not None + assert (type(e), str(e)) == error_mount + finally: + try: + vfs.umount("/test_ram") + except OSError: + pass + try: test( diff --git a/tests/extmod/vfs_blockdev_invalid2.py b/tests/extmod/vfs_blockdev_invalid2.py new file mode 100644 index 00000000000..8905f833cf7 --- /dev/null +++ b/tests/extmod/vfs_blockdev_invalid2.py @@ -0,0 +1,37 @@ +# Tests where the block device returns invalid values + +try: + import vfs + + vfs.VfsFat + memoryview +except (NameError, ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class BadDev: + SEC_SIZE = 512 + + def __init__(self, blocks): + self.blocks = blocks + + def readblocks(self, n, buf): + assert len(buf) == self.SEC_SIZE + buf[:] = bytearray(self.SEC_SIZE + 1) # Attempts to enlarge passed-in buf + + def writeblocks(self, n, buf): + pass + + def ioctl(self, op, arg): + if op == 4: # MP_BLOCKDEV_IOCTL_BLOCK_COUNT + return self.blocks + if op == 5: # MP_BLOCKDEV_IOCTL_BLOCK_SIZE + return self.SEC_SIZE + + +bdev = BadDev(512) +try: + vfs.VfsFat.mkfs(bdev) +except ValueError as e: + print("ValueError") diff --git a/tests/extmod/vfs_blockdev_invalid2.py.exp b/tests/extmod/vfs_blockdev_invalid2.py.exp new file mode 100644 index 00000000000..94274de1bb3 --- /dev/null +++ b/tests/extmod/vfs_blockdev_invalid2.py.exp @@ -0,0 +1 @@ +ValueError diff --git a/tests/extmod/vfs_posix.py b/tests/extmod/vfs_posix.py index b3ca2753ba9..22e6e07d3b6 100644 --- a/tests/extmod/vfs_posix.py +++ b/tests/extmod/vfs_posix.py @@ -97,6 +97,15 @@ def write_files_without_closing(): os.rename(temp_dir + "/test", temp_dir + "/test2") print(os.listdir(temp_dir)) +# construct new VfsPosix with absolute path +fs = vfs.VfsPosix(os.getcwd() + os.sep + temp_dir) +f = fs.open("/test", "w") +f.close() +print(sorted(os.listdir(temp_dir))) +fs.rename("/test", "/_test") +print(sorted(os.listdir(temp_dir))) +fs.remove("/_test") + # construct new VfsPosix with path argument fs = vfs.VfsPosix(temp_dir) # when VfsPosix is used the intended way via vfs.mount(), it can only be called diff --git a/tests/extmod/vfs_posix.py.exp b/tests/extmod/vfs_posix.py.exp index bd1ec7bad67..28dee4c101a 100644 --- a/tests/extmod/vfs_posix.py.exp +++ b/tests/extmod/vfs_posix.py.exp @@ -6,6 +6,8 @@ True hello next_file_no <= base_file_no True ['test2'] +['test', 'test2'] +['_test', 'test2'] ['test2'] diff --git a/tests/extmod_hardware/machine_can2.py b/tests/extmod_hardware/machine_can2.py deleted file mode 100644 index 0ecced82865..00000000000 --- a/tests/extmod_hardware/machine_can2.py +++ /dev/null @@ -1,44 +0,0 @@ -# Test machine.CAN(1) and machine.CAN(2) using loopback -# -# Single device test, assumes support for loopback and no connections to the CAN pins -# -# This test is ported from tests/ports/stm32/pyb_can2.py - -try: - from machine import CAN - - CAN(2, 125_000) -except (ImportError, ValueError): - print("SKIP") - raise SystemExit - -import time - -# Setting up each CAN peripheral independently is deliberate here, to catch -# catch cases where initialising CAN2 breaks CAN1 - -can1 = CAN(1, 125_000, mode=CAN.MODE_LOOPBACK) -can1.set_filters([(0x100, 0x700, 0)]) - -can2 = CAN(2, 125_000, mode=CAN.MODE_LOOPBACK) -can2.set_filters([(0x000, 0x7F0, 0)]) - -# Drain any old messages in RX FIFOs -for can in (can1, can2): - while can.recv(): - pass - -for id, can in ((1, can1), (2, can2)): - print("testing", id) - # message1 should only receive on can1, message2 on can2 - can.send(0x123, b"message1", 0) - can.send(0x003, "message2", 0) - time.sleep_ms(10) - did_recv = False - while res := can.recv(): - did_recv = True - print(hex(res[0]), bytes(res[1]), res[2], res[3]) - if not did_recv: - print("no rx!") - -print("done") diff --git a/tests/extmod_hardware/machine_can2.py.exp b/tests/extmod_hardware/machine_can2.py.exp deleted file mode 100644 index bfb6a5088ba..00000000000 --- a/tests/extmod_hardware/machine_can2.py.exp +++ /dev/null @@ -1,5 +0,0 @@ -testing 1 -0x123 b'message1' 0 0 -testing 2 -0x3 b'message2' 0 0 -done diff --git a/tests/extmod_hardware/machine_can_instances.py b/tests/extmod_hardware/machine_can_instances.py new file mode 100644 index 00000000000..180f4000c2b --- /dev/null +++ b/tests/extmod_hardware/machine_can_instances.py @@ -0,0 +1,74 @@ +# Test multiple concurrent CAN instances using loopback. +# Initialising in any order shouldn't break TX, RX or filtering. +# +# This test is ported from tests/ports/stm32/pyb_can_instances.py + +try: + from machine import CAN + + CAN(2, 125_000) # skip any board which doesn't have at least 2 CAN peripherals +except (ImportError, ValueError): + print("SKIP") + raise SystemExit + +import time +import unittest + +# Some boards have 3x CAN peripherals, test all three +HAS_CAN3 = True +try: + CAN(3, 125_000) +except ValueError: + HAS_CAN3 = False + + +class Test(unittest.TestCase): + def test_can12(self): + self._test_pairs([(1, 2), (2, 1)]) + + @unittest.skipUnless(HAS_CAN3, "no CAN3") + def test_can3(self): + self._test_pairs([(1, 3), (3, 1), (2, 3), (3, 2)]) + + def _test_pairs(self, seq): + for id_a, id_b in seq: + with self.subTest("Testing CAN pair", id_a=id_a, id_b=id_b): + self._test_controller_pair(id_a, id_b) + + def _test_controller_pair(self, id_a, id_b): + # Setting up each CAN peripheral independently is deliberate here, to catch + # catch cases where initialising CAN2 breaks CAN1 or vice versa + can_a = CAN(id_a, 125_000, mode=CAN.MODE_SILENT_LOOPBACK) + can_a.set_filters([(0x100, 0x700, 0)]) + + can_b = CAN(id_b, 125_000, mode=CAN.MODE_SILENT_LOOPBACK) + can_b.set_filters([(0x000, 0x7F0, 0)]) + + try: + # Drain any old messages in RX FIFOs + for can in (can_a, can_b): + while can.recv(): + pass + + for which, id, can in (("A", id_a, can_a), ("B", id_b, can_b)): + # print("testing config", which, "with controller", can) + # message1 should only receive on can_a, message2 on can_b + can.send(0x123, "message1", 0) + can.send(0x003, "message2", 0) + time.sleep_ms(10) + n_recv = 0 + while res := can.recv(): + n_recv += 1 + # print(res) + if can == can_a: + self.assertEqual(res[1], b"message1", "can_a should receive message1 only") + if can == can_b: + self.assertEqual(res[1], b"message2", "can_b should receive message2 only") + self.assertEqual(n_recv, 1, "Each instance should receive exactly 1 message") + finally: + can_a.deinit() + can_b.deinit() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/extmod_hardware/machine_sdcard_dma_align.py b/tests/extmod_hardware/machine_sdcard_dma_align.py new file mode 100644 index 00000000000..5ab7a501930 --- /dev/null +++ b/tests/extmod_hardware/machine_sdcard_dma_align.py @@ -0,0 +1,225 @@ +# Test DMA read operations when the buffer alignment in RAM varies. +# +# Runs on both pyb.SDCard and machine.SDCard with default arguments, +# although some port-specific info is needed (see below) +# +# Test requirements: +# A mostly empty FAT formatted SDCard installed in SD socket (a card with other +# files may be much slower to start the test.) +import errno +import os +import machine +import micropython +import sys +import vfs +import unittest + +from micropython import const + +# Use pyb classes on stm32, machine class otherwise +try: + from pyb import SDCard, Timer +except ImportError: + try: + from machine import SDCard, Timer + except ImportError: + print("SKIP") + raise SystemExit + +_BLOCK_SZ = const(512) +_OFFS_WIDTH = const(64) # Should be at least the cache line size plus the GC block size +_TEST_BUF_SZ = const(_BLOCK_SZ + _OFFS_WIDTH) + +MOUNT_POINT = "/sd" +FILE_PATH = "/sd/stm32_align.blk" + +# Skip the whole test if there isn't a mountable SDCard +try: + sd = SDCard() + fs = vfs.VfsFat(sd) + vfs.mount(sd, MOUNT_POINT) + vfs.umount(MOUNT_POINT) + del sd +except (OSError, ValueError, AttributeError): + print("SKIP") + raise SystemExit + + +# Set some port-specific parameters for test repeats & interrupt frequency +# (ports which can issue interrupts at a high frequency don't need to run as many +# repeats of the test in order to trigger a failure due to cache bugs.) +if "pyboard" in sys.platform: + + def make_timer(timer_cb): + # Pyboard SF6 can do at least this frequency and still run the test, + # possibly as high as 40kHz depending on the callback details. + return Timer(1, freq=35_000, callback=timer_cb, hard=True) + + REPEATS = 8 +elif "mimxrt" in sys.platform: + + def make_timer(timer_cb): + return Timer(-1, period=1, callback=timer_cb, hard=True) + + # virtual timer limited to 1kHz so run more iterations (slow test!) + REPEATS = 64 + + +def verify_contents(buf, silent=False): + # Verify that each byte in 'buf' has the value of its index in the buffer + bad_pos = [] + for i in range(_BLOCK_SZ): + bi = buf[i] + if bi != i & 0xFF: + if silent: + return False + bad_pos.append((i, bi)) + if not bad_pos: + return True + + assert not silent + + print("{} bad readback values in sector:".format(len(bad_pos)), end="") + for i, v in bad_pos: + print(" {:#x}={:#x}".format(i, v), end="") + print() + return False + + +class TestSDAlign(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.sd = SDCard() + vfs.mount(cls.sd, MOUNT_POINT) + buf = bytearray(_BLOCK_SZ) + try: + with open(FILE_PATH, "rb") as f: + rlen = f.readinto(buf) + if rlen != _BLOCK_SZ: + raise RuntimeError("Unexpected length of temporary file ", FILE_PATH, rlen) + if not verify_contents(buf): + raise RuntimeError("Corrupt test block in temporary file ", FILE_PATH) + except OSError as e: + if e.errno != errno.ENOENT: + raise e + + print("Creating new temp file...") + with open(FILE_PATH, "wb") as f: + f.write(bytes(i & 0xFF for i in range(_BLOCK_SZ))) + + # Now look for the sector which holds the temporary file + # (assume is in the first 20MB of the SD Card) + vfs.umount(MOUNT_POINT) + for b in range(1, 40960): + res = cls.sd.readblocks(b, buf) + if res != 0: + raise RuntimeError("Failed to call readblocks on SDCard:", res, "block:", b) + if verify_contents(buf, True): + print("Temporary file contents found in block {}".format(b)) + cls.block = b + return + + raise RuntimeError( + "Contents of temporary file not found near start of SDCard. Too many files?" + ) + + @classmethod + def tearDownClass(cls): + try: + os.unlink(FILE_PATH) + print("Deleted temp file") + except OSError: + pass + try: + vfs.umount(MOUNT_POINT) + except OSError: + pass + del cls.sd + + def setUp(self): + self.offs = 0 + + @micropython.native + def _test_reads_inner(self, buf, repeats=REPEATS): + for offs in range(_OFFS_WIDTH): + with self.subTest(offs=offs): + self.offs = offs + slice = memoryview(buf)[offs : offs + _BLOCK_SZ] + assert len(slice) == _BLOCK_SZ + for r in range(repeats): + self.assertEqual( + self.sd.readblocks(self.block, slice), + 0, + "Read failed for block {} offs {} repeat {}/{}".format( + self.block, offs, r, repeats + ), + ) + self.assertTrue( + verify_contents(slice), + "Verify failed for block {} offs {} repeat {}/{}".format( + self.block, offs, r, repeats + ), + ) + + def test_reads(self): + # Test reading at all available offsets in a buffer + buf = bytearray(_TEST_BUF_SZ) + # This test is the most random as any failure depends on speculative reads while + # the DMA operation is in progress. We run the test more times to increase the chance + # of hitting one of these cases, but even if the issue is present the test only fails + # once per ~250 iterations. The other tests inject explicit reads and writes so they fail + # more or less immediately. + self._test_reads_inner(buf, repeats=REPEATS * 4) + + def test_interrupted_reads(self): + # Test reading at all available offsets in a buffer, while an interrupt is + # scanning through the whole buffer + buf = bytearray(_TEST_BUF_SZ) + t = None + self.scan = 0 + self.val = None + try: + + @micropython.native + def timer_cb(_): + # Arbitrary read from somewhere in the buffer + self.val = buf[self.scan % _TEST_BUF_SZ] + self.scan += 1 + + t = make_timer(timer_cb) + self._test_reads_inner(buf) + finally: + if t: + t.deinit() + # print("scan count", self.scan, self.val) + + def test_interrupted_read_write(self): + # Test reading at all available offsets in a buffer, while an interrupt is + # writing before & after the DMA buffer + buf = bytearray(_TEST_BUF_SZ) + t = None + try: + + @micropython.native + def timer_cb(t): + # Arbitrary CPU write just before and after the buffer, trying to dirty a DMA cache line + # + # Note: we never write into a word overlapping the DMA buffer, because if the buffer is not 4-byte aligned + # sdcard_read_blocks() will do a trick to align it temporarily, and this will race with that trick and + # corrupt the memory. + offs = self.offs + if offs > 3: + buf[offs - 4] = 0x55 + offs += _BLOCK_SZ + if offs < _TEST_BUF_SZ - 3: + buf[offs] = 0x56 + + t = make_timer(timer_cb) + self._test_reads_inner(buf) + finally: + if t: + t.deinit() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/feature_check/bytearray.py.exp b/tests/feature_check/bytearray.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/byteorder.py.exp b/tests/feature_check/byteorder.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/complex.py.exp b/tests/feature_check/complex.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/const.py.exp b/tests/feature_check/const.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/coverage.py.exp b/tests/feature_check/coverage.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/fstring.py.exp b/tests/feature_check/fstring.py.exp deleted file mode 100644 index 73cdb8bcc87..00000000000 --- a/tests/feature_check/fstring.py.exp +++ /dev/null @@ -1 +0,0 @@ -a=1 diff --git a/tests/feature_check/inlineasm.py b/tests/feature_check/inlineasm.py new file mode 100644 index 00000000000..0a2858fa451 --- /dev/null +++ b/tests/feature_check/inlineasm.py @@ -0,0 +1,89 @@ +# check if the inline assembler is enabled and enumerate its capabilities. + +import sys + +fields = [] + +mpy_arch = (getattr(sys.implementation, "_mpy", 0) >> 10) & 0x0F +if 8 >= mpy_arch >= 3: + fields.append("thumb") + if mpy_arch >= 7: + fields.append("vfp") +elif 10 >= mpy_arch >= 9: + fields.append("xtensa") + if mpy_arch == 10: + fields.append("windowed") +elif mpy_arch == 11: + fields.append("rv32") +else: + raise SystemExit + +arch = fields[0] +has_asm = False +if arch == "thumb": + try: + exec(""" +@micropython.asm_thumb +def f(): + add(r0, r0, r0) +""") + has_asm = True + except SyntaxError: + pass +elif arch == "xtensa": + try: + exec(""" +@micropython.asm_xtensa +def f(): + add(a0, a0, a0) +""") + has_asm = True + except SyntaxError: + pass +elif arch == "rv32": + try: + exec(""" +@micropython.asm_rv32 +def f(): + add(a0, a0, a0) +""") + has_asm = True + except SyntaxError: + pass +if not has_asm: + raise SystemExit + +if arch == "thumb": + try: + exec(""" +@micropython.asm_thumb +def f(): + it(eq) + nop() +""") + fields.append("thumb2") + except SyntaxError: + pass +elif arch == "xtensa": + pass +elif arch == "rv32": + try: + exec(""" +@micropython.asm_rv32 +def f(): + sh1add(a0, a0, a0) +""") + fields.append("zba") + except SyntaxError: + pass + try: + exec(""" +@micropython.asm_rv32 +def f(): + cm_mva01s(s0, s1) +""") + fields.append("zcmp") + except SyntaxError: + pass + +print(",".join(fields)) diff --git a/tests/feature_check/inlineasm_rv32.py b/tests/feature_check/inlineasm_rv32.py deleted file mode 100644 index 21dd103b6c3..00000000000 --- a/tests/feature_check/inlineasm_rv32.py +++ /dev/null @@ -1,9 +0,0 @@ -# check if RISC-V 32 inline asm is supported - - -@micropython.asm_rv32 -def f(): - add(a0, a0, a0) - - -print("rv32") diff --git a/tests/feature_check/inlineasm_rv32.py.exp b/tests/feature_check/inlineasm_rv32.py.exp deleted file mode 100644 index 5eecf09c224..00000000000 --- a/tests/feature_check/inlineasm_rv32.py.exp +++ /dev/null @@ -1 +0,0 @@ -rv32 diff --git a/tests/feature_check/inlineasm_rv32_zba.py b/tests/feature_check/inlineasm_rv32_zba.py deleted file mode 100644 index 81228819042..00000000000 --- a/tests/feature_check/inlineasm_rv32_zba.py +++ /dev/null @@ -1,10 +0,0 @@ -# check if RISC-V 32 inline asm supported Zba opcodes - - -@micropython.asm_rv32 -def f(): - sh1add(a0, a0, a0) - - -f() -print("rv32_zba") diff --git a/tests/feature_check/inlineasm_rv32_zba.py.exp b/tests/feature_check/inlineasm_rv32_zba.py.exp deleted file mode 100644 index fde22f5f400..00000000000 --- a/tests/feature_check/inlineasm_rv32_zba.py.exp +++ /dev/null @@ -1 +0,0 @@ -rv32_zba diff --git a/tests/feature_check/inlineasm_thumb.py b/tests/feature_check/inlineasm_thumb.py deleted file mode 100644 index 321eab0e2f8..00000000000 --- a/tests/feature_check/inlineasm_thumb.py +++ /dev/null @@ -1,9 +0,0 @@ -# check if Thumb inline asm is supported - - -@micropython.asm_thumb -def f(): - nop() - - -print("thumb") diff --git a/tests/feature_check/inlineasm_thumb.py.exp b/tests/feature_check/inlineasm_thumb.py.exp deleted file mode 100644 index bb48e1a2f03..00000000000 --- a/tests/feature_check/inlineasm_thumb.py.exp +++ /dev/null @@ -1 +0,0 @@ -thumb diff --git a/tests/feature_check/inlineasm_thumb2.py b/tests/feature_check/inlineasm_thumb2.py deleted file mode 100644 index bc4c128baf5..00000000000 --- a/tests/feature_check/inlineasm_thumb2.py +++ /dev/null @@ -1,10 +0,0 @@ -# check if Thumb2/ARMV7M instructions are supported - - -@micropython.asm_thumb -def f(): - it(eq) - nop() - - -print("thumb2") diff --git a/tests/feature_check/inlineasm_thumb2.py.exp b/tests/feature_check/inlineasm_thumb2.py.exp deleted file mode 100644 index 05d125af9f6..00000000000 --- a/tests/feature_check/inlineasm_thumb2.py.exp +++ /dev/null @@ -1 +0,0 @@ -thumb2 diff --git a/tests/feature_check/inlineasm_xtensa.py b/tests/feature_check/inlineasm_xtensa.py deleted file mode 100644 index 2a24d39973c..00000000000 --- a/tests/feature_check/inlineasm_xtensa.py +++ /dev/null @@ -1,9 +0,0 @@ -# check if Xtensa inline asm is supported - - -@micropython.asm_xtensa -def f(): - ret_n() - - -print("xtensa") diff --git a/tests/feature_check/inlineasm_xtensa.py.exp b/tests/feature_check/inlineasm_xtensa.py.exp deleted file mode 100644 index 036142c5097..00000000000 --- a/tests/feature_check/inlineasm_xtensa.py.exp +++ /dev/null @@ -1 +0,0 @@ -xtensa diff --git a/tests/feature_check/int_64.py.exp b/tests/feature_check/int_64.py.exp deleted file mode 100644 index aef5454e662..00000000000 --- a/tests/feature_check/int_64.py.exp +++ /dev/null @@ -1 +0,0 @@ -4611686018427387904 diff --git a/tests/feature_check/int_big.py.exp b/tests/feature_check/int_big.py.exp deleted file mode 100644 index 9dfe3354d59..00000000000 --- a/tests/feature_check/int_big.py.exp +++ /dev/null @@ -1 +0,0 @@ -1000000000000000000000000000000000000000000000 diff --git a/tests/feature_check/native_check.py.exp b/tests/feature_check/native_check.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/repl_emacs_check.py.exp b/tests/feature_check/repl_emacs_check.py.exp deleted file mode 100644 index 5fe8ba1cd2d..00000000000 --- a/tests/feature_check/repl_emacs_check.py.exp +++ /dev/null @@ -1,7 +0,0 @@ -MicroPython \.\+ version -Type "help()" for more information. ->>> # Check for emacs keys in REPL ->>> t = \.\+ ->>> t == 2 -True ->>> diff --git a/tests/feature_check/repl_words_move_check.py.exp b/tests/feature_check/repl_words_move_check.py.exp deleted file mode 100644 index 5fe8ba1cd2d..00000000000 --- a/tests/feature_check/repl_words_move_check.py.exp +++ /dev/null @@ -1,7 +0,0 @@ -MicroPython \.\+ version -Type "help()" for more information. ->>> # Check for emacs keys in REPL ->>> t = \.\+ ->>> t == 2 -True ->>> diff --git a/tests/feature_check/reverse_ops.py.exp b/tests/feature_check/reverse_ops.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/set_check.py.exp b/tests/feature_check/set_check.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/slice.py.exp b/tests/feature_check/slice.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/target_info.py b/tests/feature_check/target_info.py index e95530023d7..5c133f17900 100644 --- a/tests/feature_check/target_info.py +++ b/tests/feature_check/target_info.py @@ -36,4 +36,12 @@ except NameError: float_prec = 0 -print(platform, arch, arch_flags, build, thread, float_prec, len("α") == 1) +# Detect the error reporting level (based on the length of the raised exception message). +try: + (lambda: 0)(0) +except TypeError as er: + # CIRCUITPY-CHANGE: CircuitPython only exposes .value on StopIteration (py/objexcept.c). + message = er.args[0] if er.args else "" + error_reporting = {0: "none", 27: "terse", 54: "normal", 56: "detailed"}[len(message)] + +print(platform, arch, arch_flags, build, thread, float_prec, len("α") == 1, error_reporting) diff --git a/tests/feature_check/target_info.py.exp b/tests/feature_check/target_info.py.exp deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/feature_check/tstring.py.exp b/tests/feature_check/tstring.py.exp deleted file mode 100644 index ba42b0ec666..00000000000 --- a/tests/feature_check/tstring.py.exp +++ /dev/null @@ -1 +0,0 @@ -tstring diff --git a/tests/float/float_format_ints.py b/tests/float/float_format_ints.py index 7b7b30c4b34..1f221bd5e79 100644 --- a/tests/float/float_format_ints.py +++ b/tests/float/float_format_ints.py @@ -46,6 +46,13 @@ if is_REPR_C and val_str == "2147483200.000000": val_str = "2147483520.000000" +# When using REPR_C, x86 and clang, 2147483520.0 is the same +# as 2147483100.0, the second being "simple" but rounded differently +# due to x87 extra precision on intermediates. +# Both representations are valid. +if is_REPR_C and val_str == "2147483100.000000": + val_str = "2147483520.000000" + print(val_str) # Very large positive integers can be a test for precision and resolution. diff --git a/tests/float/math_fun.py b/tests/float/math_fun.py index 05f8be08fa0..9cc2427ac70 100644 --- a/tests/float/math_fun.py +++ b/tests/float/math_fun.py @@ -43,7 +43,7 @@ ans = "{:.5g}".format(function(value)) except ValueError as e: ans = str(e) - if ans.startswith("expected a "): + if ans.startswith("expected a ") or ans == "": # CPython 3.14 changed messages to be more detailed; convert them back to simple ones ans = "math domain error" print("{}({:.5g}) = {}".format(function_name, value, ans)) diff --git a/tests/float/math_fun_special.py b/tests/float/math_fun_special.py index fcf6175af73..e5432ed32e2 100644 --- a/tests/float/math_fun_special.py +++ b/tests/float/math_fun_special.py @@ -51,7 +51,7 @@ ans = "{:.4g}".format(function(value)) except ValueError as e: ans = str(e) - if ans.startswith("expected a "): + if ans.startswith("expected a ") or ans == "": # CPython 3.14 changed messages to be more detailed; convert them back to simple ones ans = "math domain error" # a tiny error in REPR_C value for 1.5204998778 causes a wrong rounded value diff --git a/tests/float/string_fstring.py b/tests/float/string_fstring.py new file mode 100644 index 00000000000..3b4233559e3 --- /dev/null +++ b/tests/float/string_fstring.py @@ -0,0 +1,7 @@ +# Format specifiers with nested replacement fields +space = 5 +prec = 2 +print(f"{3.14:{space}.{prec}}") + +space_prec = "5.2" +print(f"{3.14:{space_prec}}") diff --git a/tests/frozen/README.md b/tests/frozen/README.md deleted file mode 100644 index bd786d5a3c4..00000000000 --- a/tests/frozen/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This is a .mpy built against the current .mpy version that can be used to test -freezing without a dependency on mpy-cross. diff --git a/tests/import/import_star.py b/tests/import/import_star.py index 2cb21b877d7..dc34577d772 100644 --- a/tests/import/import_star.py +++ b/tests/import/import_star.py @@ -57,3 +57,15 @@ print("missed detection of incorrect __all__ definition") except TypeError as er: print("TypeError triggered for bad __all__ definition") + +# 6. test when package uses __getattr__ and raises AttributeError for __getattr__("__all__") +from pkgstar_getattr_attr_er import * + +print("publicFun3" in globals()) +print(publicFun3()) + +# 7. test when package uses __getattr__ and raises ValueError for __getattr__("__all__") +try: + from pkgstar_getattr_value_er import * +except ValueError as er: + print("ValueError triggered with args", er.args) diff --git a/tests/import/pkgstar_getattr_attr_er/__init__.py b/tests/import/pkgstar_getattr_attr_er/__init__.py new file mode 100644 index 00000000000..123463d3cd6 --- /dev/null +++ b/tests/import/pkgstar_getattr_attr_er/__init__.py @@ -0,0 +1,15 @@ +_print_msg = True + + +def publicFun3(): + return 3 + + +def __getattr__(attr): + global _print_msg + if _print_msg: + # CPython calls __getattr__("__all__") twice, but MicroPython only once. + # To make the output match, only print the message once. + print("__getattr__", attr) + _print_msg = False + raise AttributeError(attr) diff --git a/tests/import/pkgstar_getattr_value_er/__init__.py b/tests/import/pkgstar_getattr_value_er/__init__.py new file mode 100644 index 00000000000..43d6cdf250f --- /dev/null +++ b/tests/import/pkgstar_getattr_value_er/__init__.py @@ -0,0 +1,3 @@ +def __getattr__(attr): + print("__getattr__", attr) + raise ValueError(attr) diff --git a/tests/inlineasm/rv32/asm_ext_zcmp.py b/tests/inlineasm/rv32/asm_ext_zcmp.py new file mode 100644 index 00000000000..619478def7f --- /dev/null +++ b/tests/inlineasm/rv32/asm_ext_zcmp.py @@ -0,0 +1,264 @@ +CMMV_TEMPLATE = """ +@micropython.asm_rv32 +def t(): + cm_mv{}({}, {}) +""" + +CMMV_TESTS = ( + ("s0", "s9", False), + ("s0", "s0", False), + ("s1", "s1", False), + ("s9", "s10", False), + ("s0", "s1", True), + ("s1", "s2", True), + ("s2", "s3", True), + ("s3", "s4", True), +) + + +def cmmv_test(op, tests): + passed = True + for lhs, rhs, success in tests: + try: + exec(CMMV_TEMPLATE.format(op, lhs, rhs)) + if success is False: + print("cm.mv{} {} {} syntax fail".format(op, lhs, rhs)) + passed = False + except SyntaxError: + if success is True: + print("cm.mv{} {} {} syntax fail".format(op, lhs, rhs)) + passed = False + if passed: + print("cm.mv{} syntax pass".format(op)) + + +cmmv_test("a01s", CMMV_TESTS) +cmmv_test("sa01", CMMV_TESTS) + + +CMPP_TEMPLATE = """ +@micropython.asm_rv32 +def t(): + cm_push({}, {}) + cm_pop{}({}, {}) +""" + + +CMPP_TESTS = ( + ("", 0, 0, False), + ("ra", 0, 0, False), + ("ra, s0", 0, 0, False), + ("ra, s0-s1", 0, 0, False), + ("ra, s0-s10", 0, 0, False), + ("{}", 0, 0, False), + ("{t0}", 0, 0, False), + ("{s0}", 0, 0, False), + ("{s0-s1}", 0, 0, False), + ("{ra, s1-s0}", 0, 0, False), + ("{ra, s0-s10}", 0, 0, False), + ("{ra}", 16, -16, False), + ("{ra, s0}", -20, 20, False), + ("{ra}", 0, 0, True), + ("{ra, s0}", 0, 0, True), + ("{ra, s0-s1}", 0, 0, True), + ("{ra, s0-s5}", 0, 0, True), + ("{ra, s0-s11}", 0, 0, True), + ("{ra}", -16, 16, True), + ("{ra, s0}", -16, 16, True), + ("{ra, s0-s1}", -16, 16, True), + ("{ra}", -32, 32, True), + ("{ra, s0}", -32, 32, True), + ("{ra, s0-s1}", -32, 32, True), + ("{ra}", -48, 48, True), + ("{ra, s0}", -48, 48, True), + ("{ra, s0-s1}", -48, 48, True), +) + + +def cmpp_test(op, tests): + passed = True + for lhs, rhs_in, rhs_out, success in tests: + try: + exec(CMPP_TEMPLATE.format(lhs, rhs_in, op, lhs, rhs_out)) + if success is False: + print("cm.push/cm.pop{} {} {} {} syntax fail".format(op, lhs, rhs_in, rhs_out)) + passed = False + except SyntaxError: + if success is True: + print("cm.push/cm.pop{} {} {} {} syntax fail".format(op, lhs, rhs_in, rhs_out)) + passed = False + if passed: + print("cm.push/cm.pop{} syntax pass".format(op)) + + +cmpp_test("", CMPP_TESTS) +cmpp_test("ret", CMPP_TESTS) +cmpp_test("retz", CMPP_TESTS) + + +CM_POP_TEMPLATE = """ +@micropython.asm_rv32 +def t(a0): + mv(t0, s0) + mv(a1, a0) + mv(s0, a0) + cm_push({{ra, s0}}, {}) + add(s0, a1, a1) + cm_pop({{ra, s0}}, {}) + bne(s0, a0, fail) + li(a0, 1) + c_j(end) + label(fail) + li(a0, 0) + label(end) + mv(s0, t0) +print("cm.push/cm.pop {}", t(0x1234) == 1) +""" + +for stack_adj in 0, 16, 32, 48: + exec(CM_POP_TEMPLATE.format(-stack_adj, stack_adj, stack_adj)) + + +CM_POPRET_TEMPLATE = """ +@micropython.asm_rv32 +def t(a0): + mv(t0, ra) + mv(a1, s0) + jal(ra, clobber) + mv(ra, t0) + bne(s0, a1, fail) + li(a0, 1) + c_j(end) + label(fail) + li(a0, 0) + label(end) + c_jr(ra) + label(clobber) + cm_push({{ra, s0}}, {}) + mv(s0, a0) + cm_popret({{ra, s0}}, {}) +print("cm.push/cm.popret {}", t(0x1234) == 1) +""" + +for stack_adj in 0, 16, 32, 48: + exec(CM_POPRET_TEMPLATE.format(-stack_adj, stack_adj, stack_adj)) + + +CM_POPRETZ_TEMPLATE = """ +@micropython.asm_rv32 +def t(a0): + mv(t0, ra) + mv(a1, s0) + jal(ra, clobber) + mv(ra, t0) + bne(a0, zero, fail) + bne(s0, a1, fail) + li(a0, 1) + c_j(end) + label(fail) + li(a0, 0) + label(end) + c_jr(ra) + label(clobber) + cm_push({{ra, s0}}, {}) + mv(s0, a0) + cm_popretz({{ra, s0}}, {}) +print("cm.push/cm.popretz {}", t(0x1234) == 1) +""" + + +for stack_adj in 0, 16, 32, 48: + exec(CM_POPRETZ_TEMPLATE.format(-stack_adj, stack_adj, stack_adj)) + + +REGLIST_TEMPLATE = """ +@micropython.asm_rv32 +def t(): + li(t6, 0) +{save} + cm_push({reglist}, 0) +{trash} + cm_pop({reglist}, 0) +{compare} + c_j(restore) + label(fail) + li(t6, 1) + label(restore) +{restore} + mv(a0, t6) +print("reglist {reglist}", t() == 0) +""" + +REG_MAP = [ + ("ra", "a0", "{ra}"), + ("s0", "a1", "{ra,s0}"), + ("s1", "a2", "{ra,s0-s1}"), + ("s2", "a3", "{ra,s0-s2}"), + ("s3", "a4", "{ra,s0-s3}"), + ("s4", "a5", "{ra,s0-s4}"), + ("s5", "a6", "{ra,s0-s5}"), + ("s6", "a7", "{ra,s0-s6}"), + ("s7", "t0", "{ra,s0-s7}"), + ("s8", "t1", "{ra,s0-s8}"), + ("s9", "t2", "{ra,s0-s9}"), + ("s10", "t3", "{ra,s0-s11}"), + ("s11", "t4", "{ra,s0-s11}"), +] + +for i in range(len(REG_MAP)): + save = "" + trash = "" + compare = "" + restore = "" + _, _, reglist = REG_MAP[i] + for j in range(i + 1): + r1, r2, _ = REG_MAP[j] + save += " mv({}, {})\n".format(r2, r1) + trash += " li({}, {})\n".format(r1, (0x1111_1111 * (j + 1)) & 0xFFFF_FFFF) + compare += " bne({}, {}, fail)\n".format(r2, r1) + restore += " mv({}, {})\n".format(r1, r2) + exec( + REGLIST_TEMPLATE.format( + save=save, trash=trash, compare=compare, restore=restore, reglist=reglist + ) + ) + + +@micropython.asm_rv32 +def test_cm_mva01s(a0, a1): + cm_push({ra, s0 - s11}, 0) + mv(s4, a0) + mv(s5, a1) + li(a0, 0) + li(a1, 0) + cm_mva01s(s4, s5) + bne(a0, s4, fail) + bne(a1, s5, fail) + li(a0, 1) + c_j(end) + label(fail) + li(a0, 0) + label(end) + cm_pop({ra, s0 - s11}, 0) + + +print("cm.mva01s", test_cm_mva01s(100, 200) == 1) + + +@micropython.asm_rv32 +def test_cm_mvsa01(a0, a1): + cm_push({ra, s0 - s11}, 0) + li(s6, 0x12345678) + li(s7, 0x87654321) + cm_mvsa01(s6, s7) + bne(a0, s6, fail) + bne(a1, s7, fail) + li(a0, 1) + c_j(end) + label(fail) + li(a0, 0) + label(end) + cm_pop({ra, s0 - s11}, 0) + + +print("cm.mvsa01", test_cm_mvsa01(100, 200) == 1) diff --git a/tests/inlineasm/rv32/asm_ext_zcmp.py.exp b/tests/inlineasm/rv32/asm_ext_zcmp.py.exp new file mode 100644 index 00000000000..62850dfff4a --- /dev/null +++ b/tests/inlineasm/rv32/asm_ext_zcmp.py.exp @@ -0,0 +1,32 @@ +cm.mva01s syntax pass +cm.mvsa01 syntax pass +cm.push/cm.pop syntax pass +cm.push/cm.popret syntax pass +cm.push/cm.popretz syntax pass +cm.push/cm.pop 0 True +cm.push/cm.pop 16 True +cm.push/cm.pop 32 True +cm.push/cm.pop 48 True +cm.push/cm.popret 0 True +cm.push/cm.popret 16 True +cm.push/cm.popret 32 True +cm.push/cm.popret 48 True +cm.push/cm.popretz 0 True +cm.push/cm.popretz 16 True +cm.push/cm.popretz 32 True +cm.push/cm.popretz 48 True +reglist {ra} True +reglist {ra,s0} True +reglist {ra,s0-s1} True +reglist {ra,s0-s2} True +reglist {ra,s0-s3} True +reglist {ra,s0-s4} True +reglist {ra,s0-s5} True +reglist {ra,s0-s6} True +reglist {ra,s0-s7} True +reglist {ra,s0-s8} True +reglist {ra,s0-s9} True +reglist {ra,s0-s11} True +reglist {ra,s0-s11} True +cm.mva01s True +cm.mvsa01 True diff --git a/tests/inlineasm/thumb/asm_thumb2_bcc.py b/tests/inlineasm/thumb/asm_thumb2_bcc.py new file mode 100644 index 00000000000..bf0ee0a48fe --- /dev/null +++ b/tests/inlineasm/thumb/asm_thumb2_bcc.py @@ -0,0 +1,65 @@ +# test bcc instructions, narrow and wide versions + + +RESULT = [] + + +TEMPLATE = """ +@micropython.asm_thumb +def t(r0): + mov(r1, r0) + + mov(r0, 10) + cmp(r1, 1) + b{}(next1) + + b(end) + + label(next1) + + mov(r0, 20) + cmp(r1, 2) + b{}_n(next2) + + b(end) + + label(next2) + mov(r0, 30) + cmp(r1, 3) + b{}_w(next3) + + b(end) + + label(next3) + mov(r0, 0) + + label(end) +""" + +try: + for code in ( + "eq", + "ne", + "cs", + "cc", + "mi", + "pl", + "vs", + "vc", + "hi", + "ls", + "ge", + "lt", + "gt", + "le", + ): + exec(TEMPLATE.format(code, code, code)) + RESULT.append("B" + code.upper()) + for i in range(4): + RESULT.append(t(i)) +except MemoryError: + print("SKIP-TOO-LARGE") + raise SystemExit + +for line in RESULT: + print(line) diff --git a/tests/inlineasm/thumb/asm_thumb2_bcc.py.exp b/tests/inlineasm/thumb/asm_thumb2_bcc.py.exp new file mode 100644 index 00000000000..06fd057ae52 --- /dev/null +++ b/tests/inlineasm/thumb/asm_thumb2_bcc.py.exp @@ -0,0 +1,70 @@ +BEQ +10 +20 +10 +10 +BNE +0 +10 +20 +30 +BCS +10 +20 +30 +0 +BCC +0 +10 +10 +10 +BMI +0 +10 +10 +10 +BPL +10 +20 +30 +0 +BVS +10 +10 +10 +10 +BVC +0 +0 +0 +0 +BHI +10 +10 +20 +30 +BLS +0 +0 +10 +10 +BGE +10 +20 +30 +0 +BLT +0 +10 +10 +10 +BGT +10 +10 +20 +30 +BLE +0 +0 +10 +10 diff --git a/tests/inlineasm/thumb/asmbitops.py b/tests/inlineasm/thumb/asm_thumb2_bitops.py similarity index 100% rename from tests/inlineasm/thumb/asmbitops.py rename to tests/inlineasm/thumb/asm_thumb2_bitops.py diff --git a/tests/inlineasm/thumb/asmbitops.py.exp b/tests/inlineasm/thumb/asm_thumb2_bitops.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmbitops.py.exp rename to tests/inlineasm/thumb/asm_thumb2_bitops.py.exp diff --git a/tests/inlineasm/thumb/asmconst.py b/tests/inlineasm/thumb/asm_thumb2_const.py similarity index 100% rename from tests/inlineasm/thumb/asmconst.py rename to tests/inlineasm/thumb/asm_thumb2_const.py diff --git a/tests/inlineasm/thumb/asmconst.py.exp b/tests/inlineasm/thumb/asm_thumb2_const.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmconst.py.exp rename to tests/inlineasm/thumb/asm_thumb2_const.py.exp diff --git a/tests/inlineasm/thumb/asmdiv.py b/tests/inlineasm/thumb/asm_thumb2_div.py similarity index 100% rename from tests/inlineasm/thumb/asmdiv.py rename to tests/inlineasm/thumb/asm_thumb2_div.py diff --git a/tests/inlineasm/thumb/asmdiv.py.exp b/tests/inlineasm/thumb/asm_thumb2_div.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmdiv.py.exp rename to tests/inlineasm/thumb/asm_thumb2_div.py.exp diff --git a/tests/inlineasm/thumb/asmit.py b/tests/inlineasm/thumb/asm_thumb2_it.py similarity index 100% rename from tests/inlineasm/thumb/asmit.py rename to tests/inlineasm/thumb/asm_thumb2_it.py diff --git a/tests/inlineasm/thumb/asmit.py.exp b/tests/inlineasm/thumb/asm_thumb2_it.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmit.py.exp rename to tests/inlineasm/thumb/asm_thumb2_it.py.exp diff --git a/tests/inlineasm/thumb/asmspecialregs.py b/tests/inlineasm/thumb/asm_thumb2_specialregs.py similarity index 100% rename from tests/inlineasm/thumb/asmspecialregs.py rename to tests/inlineasm/thumb/asm_thumb2_specialregs.py diff --git a/tests/inlineasm/thumb/asmspecialregs.py.exp b/tests/inlineasm/thumb/asm_thumb2_specialregs.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmspecialregs.py.exp rename to tests/inlineasm/thumb/asm_thumb2_specialregs.py.exp diff --git a/tests/inlineasm/thumb/asmfpaddsub.py b/tests/inlineasm/thumb/asm_vfp_addsub.py similarity index 100% rename from tests/inlineasm/thumb/asmfpaddsub.py rename to tests/inlineasm/thumb/asm_vfp_addsub.py diff --git a/tests/inlineasm/thumb/asmfpaddsub.py.exp b/tests/inlineasm/thumb/asm_vfp_addsub.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmfpaddsub.py.exp rename to tests/inlineasm/thumb/asm_vfp_addsub.py.exp diff --git a/tests/inlineasm/thumb/asmfpcmp.py b/tests/inlineasm/thumb/asm_vfp_cmp.py similarity index 100% rename from tests/inlineasm/thumb/asmfpcmp.py rename to tests/inlineasm/thumb/asm_vfp_cmp.py diff --git a/tests/inlineasm/thumb/asmfpcmp.py.exp b/tests/inlineasm/thumb/asm_vfp_cmp.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmfpcmp.py.exp rename to tests/inlineasm/thumb/asm_vfp_cmp.py.exp diff --git a/tests/inlineasm/thumb/asmfpldrstr.py b/tests/inlineasm/thumb/asm_vfp_ldrstr.py similarity index 100% rename from tests/inlineasm/thumb/asmfpldrstr.py rename to tests/inlineasm/thumb/asm_vfp_ldrstr.py diff --git a/tests/inlineasm/thumb/asmfpldrstr.py.exp b/tests/inlineasm/thumb/asm_vfp_ldrstr.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmfpldrstr.py.exp rename to tests/inlineasm/thumb/asm_vfp_ldrstr.py.exp diff --git a/tests/inlineasm/thumb/asmfpmuldiv.py b/tests/inlineasm/thumb/asm_vfp_muldiv.py similarity index 100% rename from tests/inlineasm/thumb/asmfpmuldiv.py rename to tests/inlineasm/thumb/asm_vfp_muldiv.py diff --git a/tests/inlineasm/thumb/asmfpmuldiv.py.exp b/tests/inlineasm/thumb/asm_vfp_muldiv.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmfpmuldiv.py.exp rename to tests/inlineasm/thumb/asm_vfp_muldiv.py.exp diff --git a/tests/inlineasm/thumb/asmfpsqrt.py b/tests/inlineasm/thumb/asm_vfp_sqrt.py similarity index 100% rename from tests/inlineasm/thumb/asmfpsqrt.py rename to tests/inlineasm/thumb/asm_vfp_sqrt.py diff --git a/tests/inlineasm/thumb/asmfpsqrt.py.exp b/tests/inlineasm/thumb/asm_vfp_sqrt.py.exp similarity index 100% rename from tests/inlineasm/thumb/asmfpsqrt.py.exp rename to tests/inlineasm/thumb/asm_vfp_sqrt.py.exp diff --git a/tests/inlineasm/thumb/asmbcc.py b/tests/inlineasm/thumb/asmbcc.py deleted file mode 100644 index 08967d48c74..00000000000 --- a/tests/inlineasm/thumb/asmbcc.py +++ /dev/null @@ -1,29 +0,0 @@ -# test bcc instructions -# at the moment only tests beq, narrow and wide versions - - -@micropython.asm_thumb -def f(r0): - mov(r1, r0) - - mov(r0, 10) - cmp(r1, 1) - beq(end) - - mov(r0, 20) - cmp(r1, 2) - beq_n(end) - - mov(r0, 30) - cmp(r1, 3) - beq_w(end) - - mov(r0, 0) - - label(end) - - -print(f(0)) -print(f(1)) -print(f(2)) -print(f(3)) diff --git a/tests/inlineasm/thumb/asmbcc.py.exp b/tests/inlineasm/thumb/asmbcc.py.exp deleted file mode 100644 index 39da7d1a99e..00000000000 --- a/tests/inlineasm/thumb/asmbcc.py.exp +++ /dev/null @@ -1,4 +0,0 @@ -0 -10 -20 -30 diff --git a/tests/micropython/const_annotated.py b/tests/micropython/const_annotated.py new file mode 100644 index 00000000000..4d01389e43a --- /dev/null +++ b/tests/micropython/const_annotated.py @@ -0,0 +1,18 @@ +# Test type annotations in combination with const. +# This test will only work when MICROPY_COMP_CONST and MICROPY_COMP_CONST_TUPLE are enabled. + +from micropython import const + +_X0: bool = const(True) +_X1: int = const(123) +_X2: str = const("test") +_X3: tuple = const((1, 2)) +_X4: tuple[bool, int] = const((True, 4)) +_X5: bytes = b"\x01\x02\x03" + +print(_X0) +print(_X1) +print(_X2) +print(_X3) +print(_X4) +print(_X5) diff --git a/tests/micropython/const_annotated.py.exp b/tests/micropython/const_annotated.py.exp new file mode 100644 index 00000000000..65edb58e72e --- /dev/null +++ b/tests/micropython/const_annotated.py.exp @@ -0,0 +1,6 @@ +True +123 +test +(1, 2) +(True, 4) +b'\x01\x02\x03' diff --git a/tests/micropython/heapalloc_traceback.py.native.exp b/tests/micropython/heapalloc_traceback.py.native.exp index d6ac26aa829..851eb5c7806 100644 --- a/tests/micropython/heapalloc_traceback.py.native.exp +++ b/tests/micropython/heapalloc_traceback.py.native.exp @@ -1,3 +1,3 @@ StopIteration -StopIteration: - +'StopIteration: ' +'' diff --git a/tests/micropython/import_mpy_native.py b/tests/micropython/import_mpy_native.py index 59181b203bb..84cf198b278 100644 --- a/tests/micropython/import_mpy_native.py +++ b/tests/micropython/import_mpy_native.py @@ -124,7 +124,7 @@ def open(self, path, mode): __import__(mod) print(mod, "OK") except ValueError as er: - print(mod, "ValueError", er) + print(mod, "ValueError", str(er) or "incompatible .mpy arch") # unmount and undo path addition vfs.umount("/userfs") diff --git a/tests/micropython/import_mpy_native_arch.py b/tests/micropython/import_mpy_native_arch.py new file mode 100644 index 00000000000..84f15c628a8 --- /dev/null +++ b/tests/micropython/import_mpy_native_arch.py @@ -0,0 +1,58 @@ +# test that a build which can load native code imports foo..mpy +# after foo.py and before foo.mpy + +try: + import sys, os + + sys.implementation._mpy +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +mpy_arch = sys.implementation._mpy >> 10 +if mpy_arch == 0: + # This system does not support .mpy files containing native code + print("SKIP") + raise SystemExit + +# mpy-cross -march names, indexed by the arch field of the .mpy header +arch = [ + None, + "x86", + "x64", + "armv6", + "armv6m", + "armv7m", + "armv7em", + "armv7emsp", + "armv7emdp", + "xtensa", + "xtensawin", + "rv32imc", + "rv64imc", +][mpy_arch & 0x0F] + +# a bytecode-only .mpy of an empty module, as written by mpy-cross; it loads +# on any architecture, so only the file name decides which one is imported +empty_mpy = b"\x43\x06\x00\x1f\x02\x00\x10empty.py\x00\x0f\x28\x00\x02\x01\x51\x63" + +files = { + "foo." + arch + ".mpy": empty_mpy, + "foo.mpy": empty_mpy, + "bar.mpy": empty_mpy, + "baz." + arch + ".mpy": empty_mpy, + "baz.py": b"", +} + +try: + for name, data in files.items(): + with open(name, "wb") as f: + f.write(data) + + sys.path.insert(0, ".") + for name in ("foo", "bar", "baz"): + mod = __import__(name) + print(name, mod.__file__.replace(arch, "")) +finally: + for name in files: + os.remove(name) diff --git a/tests/micropython/import_mpy_native_arch.py.exp b/tests/micropython/import_mpy_native_arch.py.exp new file mode 100644 index 00000000000..8154f72f41f --- /dev/null +++ b/tests/micropython/import_mpy_native_arch.py.exp @@ -0,0 +1,3 @@ +foo ./foo..mpy +bar ./bar.mpy +baz ./baz.py diff --git a/tests/micropython/io_badlength.py b/tests/micropython/io_badlength.py new file mode 100644 index 00000000000..646a2cd9241 --- /dev/null +++ b/tests/micropython/io_badlength.py @@ -0,0 +1,35 @@ +# Test when a use IOBase class has write/readinto which returns more data than +# requested (https://github.com/micropython/micropython/issues/18845) + +try: + import io, json +except: + print("SKIP") + raise SystemExit + + +class S(io.IOBase): + def write(self, buf): + assert len(buf) >= 0 + return 2 + + def ioctl(self, cmd, arg): + return 0 + + def readinto(self, buf): + assert len(buf) >= 0 + return 3 + + +try: + print("abc", file=S()) + print("write OK") +except OSError as e: + print("write failed, errno", e.errno) + +buf = bytearray(1) +try: + json.load(S()) + print("readinto OK") +except OSError as e: + print("read failed, errno", e.errno) diff --git a/tests/micropython/io_badlength.py.exp b/tests/micropython/io_badlength.py.exp new file mode 100644 index 00000000000..bba660dd3c8 --- /dev/null +++ b/tests/micropython/io_badlength.py.exp @@ -0,0 +1,2 @@ +write failed, errno 5 +read failed, errno 5 diff --git a/tests/micropython/native_with.py b/tests/micropython/native_with.py index 9c0b98af903..37c385a42f9 100644 --- a/tests/micropython/native_with.py +++ b/tests/micropython/native_with.py @@ -9,6 +9,7 @@ def __enter__(self): print("__enter__") def __exit__(self, a, b, c): + b = repr(b)[:10] # shorten exception to "NameError(" prefix for target compatibility print("__exit__", a, b, c) diff --git a/tests/micropython/native_with.py.exp b/tests/micropython/native_with.py.exp index 7e28663f6fc..b8083a4c66b 100644 --- a/tests/micropython/native_with.py.exp +++ b/tests/micropython/native_with.py.exp @@ -5,5 +5,5 @@ __exit__ None None None __init__ __enter__ 1 -__exit__ name 'fail' isn't defined None +__exit__ NameError( None NameError diff --git a/tests/micropython/ringio_big.py b/tests/micropython/ringio_big.py index ddbbae12a63..88d5d2cc165 100644 --- a/tests/micropython/ringio_big.py +++ b/tests/micropython/ringio_big.py @@ -8,22 +8,33 @@ print("SKIP") raise SystemExit +results = [] + try: - # The maximum possible size - micropython.RingIO(bytearray(65535)) + # The maximum possible size passed as an integer. micropython.RingIO(65534) try: - # Buffer may not be too big - micropython.RingIO(bytearray(65536)) + # Size may not be too big + micropython.RingIO(65535) except ValueError as ex: - print(type(ex)) + results.append(type(ex)) + + # Allocate a buffer for use below. + buf_64k = memoryview(bytearray(65536)) + + # The maximum possible size passed as a buffer. + micropython.RingIO(buf_64k[:-1]) try: - # Size may not be too big - micropython.RingIO(65535) + # Buffer may not be too big + micropython.RingIO(buf_64k) except ValueError as ex: - print(type(ex)) + results.append(type(ex)) + except MemoryError: print("SKIP") raise SystemExit + +for result in results: + print(result) diff --git a/tests/micropython/schedule_kbd_intr.py b/tests/micropython/schedule_kbd_intr.py new file mode 100644 index 00000000000..ec7ef5a5d07 --- /dev/null +++ b/tests/micropython/schedule_kbd_intr.py @@ -0,0 +1,72 @@ +# Test using micropython.schedule() to schedule a KeyboardInterrupt. +# Note: this test cannot use unittest because the unittest overhead +# may trigger the scheduler when we don't want it to. + +try: + from time import sleep + from micropython import schedule, kbd_intr +except ImportError: + print("SKIP") + raise SystemExit + +######################################################################## +# Test basic scheduling. + +try: + schedule(kbd_intr, None) +except ValueError: + # Scheduling `kbd_intr` is not supported on this target. + print("SKIP") + raise SystemExit + +try: + sleep(0) + sleep(0) + print("fail") +except KeyboardInterrupt: + print("KeyboardInterrupt") + +######################################################################## +# Should be able to schedule it many times without error, and that +# should only trigger one KeyboardInterrupt. + +schedule(kbd_intr, None) +schedule(kbd_intr, None) +schedule(kbd_intr, None) +schedule(kbd_intr, None) +schedule(kbd_intr, None) +schedule(kbd_intr, None) +schedule(kbd_intr, None) +schedule(kbd_intr, None) +schedule(kbd_intr, None) +schedule(kbd_intr, None) +try: + sleep(0) + sleep(0) + print("fail") +except KeyboardInterrupt: + print("KeyboardInterrupt") + +# This should not raise. +for _ in range(100): + sleep(0) + +######################################################################## +# Test nested scheduling. + + +def callback(_): + schedule(kbd_intr, None) + # This should not raise because we are in schedule context. + for _ in range(100): + sleep(0) + + +schedule(callback, None) +sleep(0) +try: + sleep(0) + sleep(0) + print("fail") +except KeyboardInterrupt: + print("KeyboardInterrupt") diff --git a/tests/micropython/schedule_kbd_intr.py.exp b/tests/micropython/schedule_kbd_intr.py.exp new file mode 100644 index 00000000000..c4ba7730c52 --- /dev/null +++ b/tests/micropython/schedule_kbd_intr.py.exp @@ -0,0 +1,3 @@ +KeyboardInterrupt +KeyboardInterrupt +KeyboardInterrupt diff --git a/tests/micropython/viper_error.py b/tests/micropython/viper_error.py index 6c5c3ba2007..e58a3c59fa0 100644 --- a/tests/micropython/viper_error.py +++ b/tests/micropython/viper_error.py @@ -1,19 +1,19 @@ # test syntax and type errors specific to viper code generation -def test(code): +def test(code, msg): try: exec(code) except (SyntaxError, ViperTypeError, NotImplementedError) as e: - print(repr(e)) + print(type(e), str(e) or msg) # viper: annotations must be identifiers -test("@micropython.viper\ndef f(a:1): pass") -test("@micropython.viper\ndef f() -> 1: pass") +test("@micropython.viper\ndef f(a:1): pass", "annotation must be an identifier") +test("@micropython.viper\ndef f() -> 1: pass", "annotation must be an identifier") # unknown type -test("@micropython.viper\ndef f(x:unknown_type): pass") +test("@micropython.viper\ndef f(x:unknown_type): pass", "unknown type 'unknown_type'") # local used before type known test( @@ -22,7 +22,8 @@ def test(code): def f(): print(x) x = 1 -""" +""", + "local 'x' used before type known", ) # type mismatch storing to local @@ -33,7 +34,8 @@ def f(): x = 1 y = [] x = y -""" +""", + "local 'x' has type 'int' but source is 'object'", ) # can't implicitly convert type to bool @@ -44,47 +46,52 @@ def f(): x = ptr(0) if x: pass -""" +""", + "can't implicitly convert 'ptr' to 'bool'", ) # incorrect return type -test("@micropython.viper\ndef f() -> int: return []") +test("@micropython.viper\ndef f() -> int: return []", "return expected 'int' but got 'object'") # can't do unary op of incompatible type -test("@micropython.viper\ndef f(x:ptr): -x") +test("@micropython.viper\ndef f(x:ptr): -x", "can't do unary op of 'ptr'") # can't do binary op between incompatible types -test("@micropython.viper\ndef f(): 1 + []") -test("@micropython.viper\ndef f(x:int, y:uint): x < y") +test("@micropython.viper\ndef f(): 1 + []", "can't do binary op between 'int' and 'object'") +test("@micropython.viper\ndef f(x:int, y:uint): x < y", "comparison of int and uint") # can't load -test("@micropython.viper\ndef f(): 1[0]") -test("@micropython.viper\ndef f(): 1[x]") +test("@micropython.viper\ndef f(): 1[0]", "can't load from 'int'") +test("@micropython.viper\ndef f(): 1[x]", "can't load from 'int'") # can't store -test("@micropython.viper\ndef f(): 1[0] = 1") -test("@micropython.viper\ndef f(): 1[x] = 1") -test("@micropython.viper\ndef f(x:int): x[0] = x") -test("@micropython.viper\ndef f(x:ptr32): x[0] = None") -test("@micropython.viper\ndef f(x:ptr32): x[x] = None") +test("@micropython.viper\ndef f(): 1[0] = 1", "can't store to 'int'") +test("@micropython.viper\ndef f(): 1[x] = 1", "can't store to 'int'") +test("@micropython.viper\ndef f(x:int): x[0] = x", "can't store to 'int'") +test("@micropython.viper\ndef f(x:ptr32): x[0] = None", "can't store 'None'") +test("@micropython.viper\ndef f(x:ptr32): x[x] = None", "can't store 'None'") # must raise an object -test("@micropython.viper\ndef f(): raise 1") +test("@micropython.viper\ndef f(): raise 1", "must raise an object") # unary ops not implemented -test("@micropython.viper\ndef f(x:int): not x") +test("@micropython.viper\ndef f(x:int): not x", "'not' not implemented") # binary op not implemented -test("@micropython.viper\ndef f(x:uint, y:uint): res = x // y") -test("@micropython.viper\ndef f(x:uint, y:uint): res = x % y") -test("@micropython.viper\ndef f(x:int): res = x in x") +test("@micropython.viper\ndef f(x:uint, y:uint): res = x // y", "div/mod not implemented for uint") +test("@micropython.viper\ndef f(x:uint, y:uint): res = x % y", "div/mod not implemented for uint") +test("@micropython.viper\ndef f(x:int): res = x in x", "binary op not implemented") + +# raise with 0 or 2 args not implemented +test("@micropython.viper\ndef f():\n try:\n x\n except:\n raise\n", "native raise") +test("@micropython.viper\ndef f(): raise Exception from Exception", "native raise") # yield (from) not implemented -test("@micropython.viper\ndef f(): yield") -test("@micropython.viper\ndef f(): yield from f") +test("@micropython.viper\ndef f(): yield", "native yield") +test("@micropython.viper\ndef f(): yield from f", "native yield") # passing a ptr to a Python function not implemented -test("@micropython.viper\ndef f(): print(ptr(1))") +test("@micropython.viper\ndef f(): print(ptr(1))", "conversion to object") # cast of a casting identifier not implemented -test("@micropython.viper\ndef f(): int(int)") +test("@micropython.viper\ndef f(): int(int)", "casting") diff --git a/tests/micropython/viper_error.py.exp b/tests/micropython/viper_error.py.exp index 51cbd6c7097..9b663b8508a 100644 --- a/tests/micropython/viper_error.py.exp +++ b/tests/micropython/viper_error.py.exp @@ -1,26 +1,28 @@ -SyntaxError('annotation must be an identifier',) -SyntaxError('annotation must be an identifier',) -ViperTypeError("unknown type 'unknown_type'",) -ViperTypeError("local 'x' used before type known",) -ViperTypeError("local 'x' has type 'int' but source is 'object'",) -ViperTypeError("can't implicitly convert 'ptr' to 'bool'",) -ViperTypeError("return expected 'int' but got 'object'",) -ViperTypeError("can't do unary op of 'ptr'",) -ViperTypeError("can't do binary op between 'int' and 'object'",) -ViperTypeError('comparison of int and uint',) -ViperTypeError("can't load from 'int'",) -ViperTypeError("can't load from 'int'",) -ViperTypeError("can't store to 'int'",) -ViperTypeError("can't store to 'int'",) -ViperTypeError("can't store to 'int'",) -ViperTypeError("can't store 'None'",) -ViperTypeError("can't store 'None'",) -ViperTypeError('must raise an object',) -ViperTypeError("'not' not implemented",) -ViperTypeError('div/mod not implemented for uint',) -ViperTypeError('div/mod not implemented for uint',) -ViperTypeError('binary op not implemented',) -NotImplementedError('native yield',) -NotImplementedError('native yield',) -NotImplementedError('conversion to object',) -NotImplementedError('casting',) + annotation must be an identifier + annotation must be an identifier + unknown type 'unknown_type' + local 'x' used before type known + local 'x' has type 'int' but source is 'object' + can't implicitly convert 'ptr' to 'bool' + return expected 'int' but got 'object' + can't do unary op of 'ptr' + can't do binary op between 'int' and 'object' + comparison of int and uint + can't load from 'int' + can't load from 'int' + can't store to 'int' + can't store to 'int' + can't store to 'int' + can't store 'None' + can't store 'None' + must raise an object + 'not' not implemented + div/mod not implemented for uint + div/mod not implemented for uint + binary op not implemented + native raise + native raise + native yield + native yield + conversion to object + casting diff --git a/tests/micropython/viper_unop.py b/tests/micropython/viper_unop.py index 61cbd5125f1..80c247a581e 100644 --- a/tests/micropython/viper_unop.py +++ b/tests/micropython/viper_unop.py @@ -29,3 +29,36 @@ def inv(x: int) -> int: print(inv(0)) print(inv(1)) print(inv(-2)) + + +@micropython.viper +def posc() -> int: + x = -1 + x1 = +x + assert x == -1 + return x1 + + +print(posc()) + + +@micropython.viper +def negc() -> int: + x = -1 + x1 = -x + assert x == -1 + return x1 + + +print(negc()) + + +@micropython.viper +def invc() -> int: + x = -1 + x1 = ~x + assert x == -1 + return x1 + + +print(invc()) diff --git a/tests/micropython/viper_unop.py.exp b/tests/micropython/viper_unop.py.exp index 6d93312caa1..9e9a5f4715f 100644 --- a/tests/micropython/viper_unop.py.exp +++ b/tests/micropython/viper_unop.py.exp @@ -7,3 +7,6 @@ -1 -2 1 +-1 +1 +0 diff --git a/tests/micropython/viper_with.py b/tests/micropython/viper_with.py index 40fbf6fb315..833a6babe7d 100644 --- a/tests/micropython/viper_with.py +++ b/tests/micropython/viper_with.py @@ -9,6 +9,7 @@ def __enter__(self): print("__enter__") def __exit__(self, a, b, c): + b = repr(b)[:10] # shorten exception to "NameError(" prefix for target compatibility print("__exit__", a, b, c) diff --git a/tests/micropython/viper_with.py.exp b/tests/micropython/viper_with.py.exp index 7e28663f6fc..b8083a4c66b 100644 --- a/tests/micropython/viper_with.py.exp +++ b/tests/micropython/viper_with.py.exp @@ -5,5 +5,5 @@ __exit__ None None None __init__ __enter__ 1 -__exit__ name 'fail' isn't defined None +__exit__ NameError( None NameError diff --git a/tests/misc/cexample_subclass.py b/tests/misc/cexample_subclass.py index 9f52a2c737a..6857788e5dc 100644 --- a/tests/misc/cexample_subclass.py +++ b/tests/misc/cexample_subclass.py @@ -2,6 +2,7 @@ try: from cexample import AdvancedTimer + import time # used to skip this test on minimal unix variant except ImportError: print("SKIP") raise SystemExit diff --git a/tests/misc/features.py b/tests/misc/features.py index 455b44fb1ef..3db88118144 100644 --- a/tests/misc/features.py +++ b/tests/misc/features.py @@ -103,7 +103,7 @@ class c25: x = c25() print(x.x) - raise + raise Exception except: print(26) print(27 + zero) diff --git a/tests/misc/rge_sm.py b/tests/misc/rge_sm.py index 33aa4edb742..3c30817952f 100644 --- a/tests/misc/rge_sm.py +++ b/tests/misc/rge_sm.py @@ -82,7 +82,11 @@ def singleTraj(system, trajStart, h=0.02, tend=1.0): # compute the trajectory rk = RungeKutta(system, trajStart, tstart, h) - rk.solve(tend) + try: + rk.solve(tend) + except MemoryError: + print("SKIP") + raise SystemExit # print out trajectory diff --git a/tests/multi_extmod/machine_can_04_tx_order.py b/tests/multi_extmod/machine_can_04_tx_order.py index 204bdafd59d..9efd9088c94 100644 --- a/tests/multi_extmod/machine_can_04_tx_order.py +++ b/tests/multi_extmod/machine_can_04_tx_order.py @@ -1,9 +1,19 @@ from machine import CAN import time from random import seed, randrange +import sys import micropython +# The Alif port has an opaque send queue. The Alif CAN controller +# provides no information about the slot number where the message +# is stored, and it does not allow to cancel specific messages. +# This test needs the slot number, which is not available. + +if "alif" in sys.platform: + print("SKIP") + raise SystemExit + micropython.alloc_emergency_exception_buf(256) seed(0) @@ -103,7 +113,10 @@ def irq_send(can): def instance1(): # note: this test can pass with hard=True, but in a debug build # the completion IRQ may race ahead of setting tx_queue[idx], below - can.irq(irq_send, trigger=can.IRQ_TX, hard=False) + if "mimxrt" in sys.platform: + can.irq(irq_send, trigger=can.IRQ_TX, hard=True) + else: + can.irq(irq_send, trigger=can.IRQ_TX, hard=False) data = bytearray(MSG_LEN) multitest.next() diff --git a/tests/multi_extmod/machine_can_05_tx_prio_cancel.py b/tests/multi_extmod/machine_can_05_tx_prio_cancel.py index 64756a1a1af..c3f67b372df 100644 --- a/tests/multi_extmod/machine_can_05_tx_prio_cancel.py +++ b/tests/multi_extmod/machine_can_05_tx_prio_cancel.py @@ -1,5 +1,16 @@ from machine import CAN import time +import sys + +# The Alif port has an opaque send queue. The Alif CAN controller +# provides no information about the slot number where the message +# is stored, and it does not allow to cancel specific messages. +# This test needs both the slot number, and uses cancel_send() for +# dedicated messages, which both is not available. + +if "alif" in sys.platform: + print("SKIP") + raise SystemExit # Check that cancelling a low priority outgoing message and replacing it with a # high priority message causes it to be transmitted successfully onto a busy bus @@ -26,6 +37,14 @@ def instance0(): multitest.next() + # don't babble until we know instance1 is ready to receive, or this instance + # may go to Error Passive while instance1 is still initialising CAN (meaning + # the "babble" won't saturate the bus, due to the Suspend Transmission + # requirement) + multitest.wait("instance1 ready") + + bcast_countdown = 5 + # "Babble" medium priority messages onto the bus to prevent # instance1() from sending anything lower priority than this while len(recv) < ITERS: @@ -33,6 +52,12 @@ def instance0(): can.send(id, b"BABBLE", CAN.FLAG_EXT_ID) if len(recv) >= ITERS: break + if bcast_countdown > 0: + # queue some "babble" messages onto the bus before signalling to + # instance1 that it can start trying to send + bcast_countdown -= 1 + if not bcast_countdown: + multitest.broadcast("instance0 babbling") print("received", ITERS, "messages") for can_id in recv: @@ -66,10 +91,16 @@ def irq_send(can): def instance1(): - global last_idx + global last_idx, total_cancels can.irq(irq_send, trigger=can.IRQ_TX, hard=True) multitest.next() + multitest.broadcast("instance1 ready") + + # make sure instance0 can queue outgoing medium-priority + # babble before we start trying to send, so we're trying to + # send onto an already busy bus + multitest.wait("instance0 babbling") for i in range(ITERS): # Fill the transmit queue with low priority messages (all extended IDs) @@ -95,6 +126,8 @@ def instance1(): # try and cancel the last message we queued res = can.cancel_send(last_idx) print(i, "cancel result", res) + if ("mimxrt" in sys.platform) and res: + total_cancels += 1 # send a high priority message, that we expect to go out idx = can.send(0x500 + i, b"HIPRIO", CAN.FLAG_EXT_ID) diff --git a/tests/multi_extmod/machine_can_07_error_states.py b/tests/multi_extmod/machine_can_07_error_states.py index e7eec513ee1..f7071a8bd77 100644 --- a/tests/multi_extmod/machine_can_07_error_states.py +++ b/tests/multi_extmod/machine_can_07_error_states.py @@ -108,6 +108,8 @@ def instance1(): # Send a single message to the receiver, to verify it's working can.send(_ID, b"PAYLOAD") + time.sleep_ms(50) # irq_sender should fire during this window + active_counters = can.get_counters() # print(active_counters) # DEBUG @@ -189,15 +191,19 @@ def instance1(): # with this test setup, as Bus Off requires more than just "normal" frame # transmit errors. - # restarting the controller may cause it to leave its error state, or not, depending - # on the implementation - but it shouldn't cause any recovery issues. Also cancels all pending TX - # (note: have to do this before 'fix baud' or we create a race condition for pending tx) - can.restart() - - # tell the receiver to go back to a valid baud rate + # tell the receiver to go back to a valid baud rate, causing the pending tx + # to be sent. multitest.broadcast("fix baud") multitest.wait("fixed baud") + # Restarting the controller may cause it to leave its error state, or not, depending + # on the implementation - but it shouldn't cause any recovery issues. Also cancels all pending TX. + # If restart() clears the TEC and REC error counters, resetting the error state to ACTIVE, + # and if that is done while instance0 is still trying to send at the wrong baud rate, + # the error state rushes up again pretty fast. Therefore the baud rate is fixed before + # calling restart(). + can.restart() + idx_more = can.send(_ID, b"MOREMORE") time.sleep_ms(50) # irq_sender should fire during this window print("queued moremore", idx_more is not None) diff --git a/tests/multi_extmod/machine_can_07_error_states.py.exp b/tests/multi_extmod/machine_can_07_error_states.py.exp index 158ae63bfbc..c2731f20905 100644 --- a/tests/multi_extmod/machine_can_07_error_states.py.exp +++ b/tests/multi_extmod/machine_can_07_error_states.py.exp @@ -33,5 +33,6 @@ one over thresh True no new warning True counted passive True irq sent True +irq sent True queued moremore True done diff --git a/tests/multi_extmod/machine_can_08_init_mode.py b/tests/multi_extmod/machine_can_08_init_mode.py index 459e6180ab6..730edfac7e5 100644 --- a/tests/multi_extmod/machine_can_08_init_mode.py +++ b/tests/multi_extmod/machine_can_08_init_mode.py @@ -1,6 +1,7 @@ from machine import CAN from micropython import const import time +import sys # instance0 transitions through various modes, instance1 # listens for various messages (or not) @@ -29,7 +30,7 @@ def irq_print(can): print("recv", hex(can_id), bytes(data)) else: silent_rx_count += 1 - if flags & can.IRQ_TX: # note: only enabled on instance1 to avoid race conditions + elif flags & can.IRQ_TX: # note: only enabled on instance1 to avoid race conditions print("send", "failed" if flags & can.IRQ_TX_FAILED else "ok") @@ -66,8 +67,14 @@ def instance0(): multitest.broadcast("silent") multitest.wait("silent done") # we should have received the message from instance1 many times, as instance0 won't have ACKed it - print("silent_rx_count", silent_rx_count > 5) - can.cancel_send(idx) + # create a dummy "OK" for MIMXRT, since it on receives ACKed messages in SILENT mode. + if ("mimxrt" in sys.platform) or ("alif" in sys.platform): + print("silent_rx_count True") + else: + print("silent_rx_count", silent_rx_count > 5) + # Cancel the sent message only if it was accepted for sending. + if idx is not None: + can.cancel_send(idx) reinit_with_mode(can.MODE_SILENT_LOOPBACK) print("Silent Loopback", "MODE_SILENT_LOOPBACK" in str(can)) @@ -97,6 +104,9 @@ def instance1(): idx = can.send(0x53, b"Silent2") time.sleep_ms(20) can.cancel_send(idx) + # mimxrt does not give TX failed interrupts on cancel_send. + if "mimxrt" in sys.platform: + print("send failed") multitest.broadcast("silent done") multitest.wait("normal done") diff --git a/tests/perf_bench/core_import_mpy_multi.py b/tests/perf_bench/core_import_mpy_multi.py index 67deec05088..55aa379e2bf 100644 --- a/tests/perf_bench/core_import_mpy_multi.py +++ b/tests/perf_bench/core_import_mpy_multi.py @@ -1,8 +1,9 @@ # Test performance of importing an .mpy file many times. -import sys, io, vfs - -if not hasattr(io, "IOBase"): +try: + import sys, vfs + from io import IOBase +except ImportError: print("SKIP") raise SystemExit @@ -26,7 +27,7 @@ def f(): file_data = b'M\x06\x00\x1e\x14\x03\x0etest.py\x00\x0f\x02A\x00\x02f\x00\x0cresult\x00/-5#\x82I\x81{\x81w\x82/\x81\x05\x81\x17Iom\x82\x13\x06arg\x00\x05\x1cthis will be a string object\x00\x06\x1bthis will be a bytes object\x00\n\x07\x05\x0bconst tuple\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x81\\\x10\n\x01\x89\x07d`T2\x00\x10\x024\x02\x16\x022\x01\x16\x03"\x80{\x16\x04Qc\x02\x81d\x00\x08\x02(DD\x11\x05\x16\x06\x10\x02\x16\x072\x00\x16\x082\x01\x16\t2\x02\x16\nQc\x03`\x1a\x08\x08\x12\x13@\xb1\xb0\x18\x13Qc@\t\x08\t\x12` Qc@\t\x08\n\x12``Qc\x82@ \x0e\x03\x80\x08+)##\x12\x0b\x12\x0c\x12\r\x12\x0e*\x04Y\x12\x0f\x12\x10\x12\x11*\x03Y#\x00\xc0#\x01\xc0#\x02\xc0Qc' -class File(io.IOBase): +class File(IOBase): def __init__(self): self.off = 0 diff --git a/tests/perf_bench/core_import_mpy_single.py b/tests/perf_bench/core_import_mpy_single.py index f472bb64762..755e4159487 100644 --- a/tests/perf_bench/core_import_mpy_single.py +++ b/tests/perf_bench/core_import_mpy_single.py @@ -2,9 +2,10 @@ # The first import of a module will intern strings that don't already exist, and # this test should be representative of what happens in a real application. -import sys, io, vfs - -if not hasattr(io, "IOBase"): +try: + import sys, vfs + from io import IOBase +except ImportError: print("SKIP") raise SystemExit @@ -81,7 +82,7 @@ def f1(): file_data = b"M\x06\x00\x1e\x81=\x1e\x0etest.py\x00\x0f\x04A0\x00\x04A1\x00\x04f0\x00\x04f1\x00\x0cresult\x00/-5\x04a0\x00\x04a1\x00\x04a2\x00\x04a3\x00\x13\x15\x17\x19\x1b\x1d\x1f!#%')+1379;=?ACEGIKMOQSUWY[]_acegikmoqsuwy{}\x7f\x81\x01\x81\x03\x81\x05\x81\x07\x81\t\x81\x0b\x81\r\x81\x0f\x81\x11\x81\x13\x81\x15\x81\x17\x81\x19\x81\x1b\x81\x1d\x81\x1f\x81!\x81#\x81%\x81'\x81)\x81+\x81-\x81/\x811\x813\x815\x817\x819\x81;\x81=\x81?\x81A\x81C\x81E\x81G\x81I\x81K\x81M\x81O\x81Q\x81S\x81U\x81W\x81Y\x81[\x81]\x81_\x81a\x81c\x81e\x81g\x81i\x81k\x81m\x81o\x81q\x81s\x81u\x81w\x81y\x81{\x81}\x81\x7f\x82\x01\x82\x03\x82\x05\x82\x07\x82\t\x82\x0b\x82\r\x82\x0f\x82\x11\x82\x13\x82\x15\x82\x17\x82\x19\x82\x1b\x82\x1d\x82\x1f\x82!\x82#\x82%\x82'\x82)\x82+\x82-\x82/\x821\x823\x825\x827\x829\x82;\x82=\x82?\x82A\x82E\x82G\x82I\x82K\nname0\x00\nname1\x00\nname2\x00\nname3\x00\nname4\x00\nname5\x00\nname6\x00\nname7\x00\nname8\x00\nname9\x00$quite_a_long_name0\x00$quite_a_long_name1\x00$quite_a_long_name2\x00$quite_a_long_name3\x00$quite_a_long_name4\x00$quite_a_long_name5\x00$quite_a_long_name6\x00$quite_a_long_name7\x00$quite_a_long_name8\x00$quite_a_long_name9\x00&quite_a_long_name10\x00&quite_a_long_name11\x00\x05\x1ethis will be a string object 0\x00\x05\x1ethis will be a string object 1\x00\x05\x1ethis will be a string object 2\x00\x05\x1ethis will be a string object 3\x00\x05\x1ethis will be a string object 4\x00\x05\x1ethis will be a string object 5\x00\x05\x1ethis will be a string object 6\x00\x05\x1ethis will be a string object 7\x00\x05\x1ethis will be a string object 8\x00\x05\x1ethis will be a string object 9\x00\x06\x1dthis will be a bytes object 0\x00\x06\x1dthis will be a bytes object 1\x00\x06\x1dthis will be a bytes object 2\x00\x06\x1dthis will be a bytes object 3\x00\x06\x1dthis will be a bytes object 4\x00\x06\x1dthis will be a bytes object 5\x00\x06\x1dthis will be a bytes object 6\x00\x06\x1dthis will be a bytes object 7\x00\x06\x1dthis will be a bytes object 8\x00\x06\x1dthis will be a bytes object 9\x00\n\x07\x05\rconst tuple 0\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 1\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 2\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 3\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 4\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 5\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 6\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 7\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 8\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 9\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x82d\x10\x12\x01i@i@\x84\x18\x84\x1fT2\x00\x10\x024\x02\x16\x02T2\x01\x10\x034\x02\x16\x032\x02\x16\x042\x03\x16\x05\"\x80{\x16\x06Qc\x04\x82\x0c\x00\n\x02($$$\x11\x07\x16\x08\x10\x02\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04@\t\x08\n\x81\x0b Qc@\t\x08\x0b\x81\x0b@Qc@\t\x08\x0c\x81\x0b`QcH\t\n\r\x81\x0b` Qc\x82\x14\x00\x0c\x03h`$$$\x11\x07\x16\x08\x10\x03\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04H\t\n\n\x81\x0b``QcH\t\n\x0b\x81\x0b\x80\x07QcH\t\n\x0c\x81\x0b\x80\x08QcH\t\n\r\x81\x0b\x80\tQc\xa08P:\x04\x80\x0b13///---997799<\x1f%\x1f\"\x1f%)\x1f\"//\x12\x0e\x12\x0f\x12\x10\x12\x11\x12\x12\x12\x13\x12\x14*\x07Y\x12\x15\x12\x16\x12\x17\x12\x18\x12\x19\x12\x1a\x12\x08\x12\x07*\x08Y\x12\x1b\x12\x1c\x12\t\x12\x1d\x12\x1e\x12\x1f*\x06Y\x12 \x12!\x12\"\x12#\x12$\x12%*\x06Y\x12&\x12'\x12(\x12)\x12*\x12+*\x06Y\x12,\x12-\x12.\x12/\x120*\x05Y\x121\x122\x123\x124\x125*\x05Y\x126\x127\x128\x129\x12:*\x05Y\x12;\x12<\x12=\x12>\x12?\x12@\x12A\x12B\x12C\x12D\x12E*\x0bY\x12F\x12G\x12H\x12I\x12J\x12K\x12L\x12M\x12N\x12O\x12P*\x0bY\x12Q\x12R\x12S\x12T\x12U\x12V\x12W\x12X\x12Y\x12Z*\nY\x12[\x12\\\x12]\x12^\x12_\x12`\x12a\x12b\x12c\x12d*\nY\x12e\x12f\x12g\x12h\x12i\x12j\x12k\x12l\x12m\x12n\x12o*\x0bY\x12p\x12q\x12r\x12s\x12t\x12u\x12v\x12w\x12x\x12y\x12z*\x0bY\x12{\x12|\x12}\x12~\x12\x7f\x12\x81\x00\x12\x81\x01\x12\x81\x02\x12\x81\x03\x12\x81\x04*\nY\x12\x81\x05\x12\x81\x06\x12\x81\x07\x12\x81\x08\x12\x81\t\x12\x81\n\x12\x81\x0b\x12\x81\x0c\x12\x81\r\x12\x81\x0e\x12\x81\x0f*\x0bY\x12\x81\x10\x12\x81\x11\x12\x81\x12\x12\x81\x13\x12\x81\x14\x12\x81\x15\x12\x81\x16\x12\x81\x17\x12\x81\x18\x12\x81\x19*\nY\x12\x81\x1a\x12\x81\x1b\x12\x81\x1c\x12\x81\x1d\x12\x81\x1e\x12\x81\x1f\x12\x81 \x12\x81!\x12\x81\"\x12\x81#\x12\x81$*\x0bY\x12\x81%\x12\x81&*\x02Y\x12\x81'\x12\x81(\x12\x81)\x12\x81*\x12\x81+\x12\x81,\x12\x81-\x12\x81.\x12\x81/\x12\x810*\nY\x12\x811\x12\x812\x12\x813\x12\x814*\x04Y\x12\x815\x12\x816\x12\x817\x12\x818*\x04Y\x12\x819\x12\x81:\x12\x81;\x12\x81<*\x04YQc\x87p\x08@\x05\x80###############################\x00\xc0#\x01\xc0#\x02\xc0#\x03\xc0#\x04\xc0#\x05\xc0#\x06\xc0#\x07\xc0#\x08\xc0#\t\xc0#\n\xc0#\x0b\xc0#\x0c\xc0#\r\xc0#\x0e\xc0#\x0f\xc0#\x10\xc0#\x11\xc0#\x12\xc0#\x13\xc0#\x14\xc0#\x15\xc0#\x16\xc0#\x17\xc0#\x18\xc0#\x19\xc0#\x1a\xc0#\x1b\xc0#\x1c\xc0#\x1d\xc0Qc" -class File(io.IOBase): +class File(IOBase): def __init__(self): self.off = 0 diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 40aac16c16f..de59db2dc38 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -598,7 +598,7 @@ def main(): cmd_args = cmd_parser.parse_args() # clear search path to make sure tests use only builtin modules and those in extmod - os.environ["MICROPYPATH"] = os.pathsep.join((".frozen", "../extmod")) + os.environ["MICROPYPATH"] = os.pathsep.join((".frozen", base_path("../extmod"))) test_files = prepare_test_file_list(cmd_args.files) max_instances = max(t[1] for t in test_files) diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index 16182bc8a9f..2b17655095c 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -8,11 +8,13 @@ import subprocess import sys import argparse +import re from glob import glob from test_utils import ( base_path, pyboard, + set_injected_prologue, get_test_instance, prepare_script_for_target, create_test_report, @@ -101,15 +103,14 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): print(test_file + ": ", end="") # Check if test should be skipped - skip = ( - skip_complex - and test_file.find("bm_fft") != -1 - or skip_native - and test_file.find("viper_") != -1 - ) - if skip: - test_results.append((test_file, "skip", "")) - print("SKIP") + skip_reason = None + if skip_complex and test_file.endswith(("bm_fft.py", "misc_mandel.py")): + skip_reason = "complex not supported" + elif skip_native and test_file.find("viper_") != -1: + skip_reason = "native not supported" + if skip_reason: + test_results.append((test_file, "skip", skip_reason)) + print("SKIP:", skip_reason) continue # Create test script @@ -120,6 +121,19 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): test_script += f.read() test_script += b"bm_run(%u, %u)\n" % (param_n, param_m) + # Search for the bm_params dict, to extract the minimum memory required. + m = re.search(rb"bm_params = {\s+\((\d+), (\d+)\):", test_script) + if not m: + print(f"Test file '{test_file}' doesn't contain valid 'bm_params'") + sys.exit(2) + min_m = int(m.group(2)) + + # Skip the test if the target doesn't have enough memory. + if param_m < min_m: + test_results.append((test_file, "skip", "too large")) + print("SKIP: too large") + continue + # Write full test script if needed if 0: with open("%s.full" % test_file, "wb") as f: @@ -127,7 +141,7 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): # Process script through mpy-cross if needed if hasattr(target, "enter_raw_repl") or args.via_mpy: - crash, test_script_target = prepare_script_for_target(args, script_text=test_script) + crash, test_script_target = prepare_script_for_target(args, test_script, test_file) if crash: test_results.append((test_file, "fail", "preparation")) print("CRASH:", test_script_target) @@ -278,6 +292,12 @@ def main(): cmd_parser.add_argument("--heapsize", help="heapsize to use (use default if not specified)") cmd_parser.add_argument("--via-mpy", action="store_true", help="compile code to .mpy first") cmd_parser.add_argument("--mpy-cross-flags", default="", help="flags to pass to mpy-cross") + cmd_parser.add_argument( + "--begin", + metavar="PROLOGUE", + default=None, + help="prologue python file to execute before module import", + ) cmd_parser.add_argument( "-r", "--result-dir", @@ -295,6 +315,12 @@ def main(): compute_diff(args.N[0], args.M[0], args.diff_score) sys.exit(0) + prologue = "" + if args.begin: + with open(args.begin, "rt") as source: + prologue = source.read() + set_injected_prologue(prologue) + # N, M = 50, 25 # esp8266 # N, M = 100, 100 # pyboard, esp32 # N, M = 1000, 1000 # PC @@ -314,14 +340,10 @@ def main(): args.mpy_cross_flags = "-march=armv7m" if len(args.files) == 0: - tests_skip = ("benchrun.py",) - if M <= 25: - # These scripts are too big to be compiled by the target - tests_skip += ("bm_chaos.py", "bm_hexiom.py", "misc_raytrace.py") tests = sorted( BENCH_SCRIPT_DIR + test_file for test_file in os.listdir(BENCH_SCRIPT_DIR) - if test_file.endswith(".py") and test_file not in tests_skip + if test_file.endswith(".py") and test_file != "benchrun.py" ) else: tests = sorted(args.files) diff --git a/tests/run-tests.py b/tests/run-tests.py index 9ac8fdd0fbb..35eb2eb3aa2 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -29,7 +29,6 @@ get_results_filename, convert_device_shortcut_to_real_device, get_test_instance, - prepare_script_for_target, create_test_report, FLAKY_REASON_PREFIX, ) @@ -90,11 +89,12 @@ # Remove them from the below when they work. "native": ( # These require raise_varargs. - "basics/gen_yield_from_close.py", "basics/try_finally_return2.py", "basics/try_reraise.py", "basics/try_reraise2.py", - "misc/features.py", + # CIRCUITPY-CHANGE: our traceback deviation in this test uses a bare + # raise, which is raise_varargs with no argument. + "basics/gen_yield_from_close.py", # These require checking for unbound local. "basics/annotate_var.py", "basics/del_deref.py", @@ -178,6 +178,7 @@ "extmod/binascii_a2b_base64.py", "extmod/deflate_compress_memory_error.py", # tries to allocate unlimited memory "extmod/re_stack_overflow.py", + "extmod/re_stack_overflow2.py", "extmod/time_res.py", "extmod/vfs_posix.py", "extmod/vfs_posix_enoent.py", @@ -197,6 +198,25 @@ ), } +# Tests to skip when MICROPY_ERROR_REPORTING is at a certain level. +error_reporting_tests_to_skip = { + # Skip at level MICROPY_ERROR_REPORTING_TERSE. + "terse": ( + "cmdline/repl_paste.py", + # This test needs updates before being removed from this list. + "extmod/vfs_blockdev_invalid.py", + "micropython/heapalloc_exc_compressed.py", + "micropython/heapalloc_exc_compressed_emg_exc.py", + "micropython/opt_level_lineno.py", + "misc/print_exception.py", + "misc/sys_settrace_features.py", + ), +} +# Skip at level MICROPY_ERROR_REPORTING_NONE. +error_reporting_tests_to_skip["none"] = error_reporting_tests_to_skip["terse"] + ( + "extmod/asyncio_gather_notimpl.py", +) + # Tests with known intermittent failures. These tests still run, but failures # are reclassified as "ignored" instead of "fail" so they don't affect the CI # exit code. Paths are relative to the tests/ directory (must match test_file @@ -239,6 +259,8 @@ "extmod/uctypes_le_float.py", "extmod/uctypes_native_float.py", "extmod/uctypes_sizeof_float.py", + "extmod/vfs_rom.py", + "micropython/const_float.py", "misc/rge_sm.py", "ports/unix/ffi_float.py", "ports/unix/ffi_float2.py", @@ -267,6 +289,7 @@ "extmod/time_mktime.py", "extmod/time_res.py", "extmod/tls_sslcontext_ciphers.py", + "extmod/vfs_blockdev_invalid2.py", "extmod/vfs_fat_fileio1.py", "extmod/vfs_fat_finaliser.py", "extmod/vfs_fat_more.py", @@ -283,6 +306,7 @@ "micropython/import_mpy_invalid.py", "micropython/import_mpy_native.py", "micropython/import_mpy_native_gc.py", + "micropython/ringio_big.py", "misc/non_compliant.py", "misc/rge_sm.py", ) @@ -326,11 +350,9 @@ def platform_to_port(platform): def detect_inline_asm_arch(pyb, args): - for arch in ("rv32", "thumb", "xtensa"): - output = run_feature_check(pyb, args, "inlineasm_{}.py".format(arch)) - if output.strip() == arch.encode(): - return arch - return None + output = run_feature_check(pyb, args, "inlineasm.py").decode().strip() + arch, *features = output.split(",") + return arch, features def map_rv32_arch_flags(flags): @@ -350,12 +372,12 @@ def detect_test_platform(pyb, args): if output.endswith(b"CRASH"): raise ValueError("cannot detect platform: {}".format(output)) # CIRCUITPY-CHANGE: sys.platform is the first field and can contain spaces ("Atmel SAMD21"). - platform, arch, arch_flags, build, thread, float_prec, unicode = ( - str(output, "ascii").strip().rsplit(None, 6) + platform, arch, arch_flags, build, thread, float_prec, unicode, error_reporting = ( + str(output, "ascii").strip().rsplit(None, 7) ) if arch == "None": arch = None - inlineasm_arch = detect_inline_asm_arch(pyb, args) + inlineasm_arch, inlineasm_features = detect_inline_asm_arch(pyb, args) if thread == "None": thread = None float_prec = int(float_prec) @@ -373,10 +395,12 @@ def detect_test_platform(pyb, args): if arch_flags: args.mpy_cross_flags += " -march-flags=" + ",".join(arch_flags) args.inlineasm_arch = inlineasm_arch + args.inlineasm_features = inlineasm_features args.build = build args.thread = thread args.float_prec = float_prec args.unicode = unicode + args.error_reporting = error_reporting # Print the detected information about the target. print("platform={}".format(platform), end="") @@ -446,7 +470,9 @@ def detect_target_wiring_script(pyb, args): ] -def run_micropython(pyb, args, test_file, test_file_abspath, is_special=False): +def run_micropython( + pyb, args, test_file, test_file_abspath, is_special=False, is_feature_check=False +): had_crash = False if pyb is None: # run on PC @@ -456,12 +482,17 @@ def run_micropython(pyb, args, test_file, test_file_abspath, is_special=False): if is_special: # check for any cmdline options needed for this test - args = [MICROPYTHON] + cmdlist = [MICROPYTHON] + send_sigint = False with open(test_file, "rb") as f: - line = f.readline() - if line.startswith(b"# cmdline:"): - # subprocess.check_output on Windows only accepts strings, not bytes - args += [str(c, "utf-8") for c in line[10:].strip().split()] + for line in f: + if line.startswith(b"# cmdline:"): + # subprocess.check_output on Windows only accepts strings, not bytes + cmdlist += [str(c, "utf-8") for c in line[10:].strip().split()] + elif line.startswith(b"# sigint:"): + send_sigint = True + elif not line.startswith(b"#"): + break # run the test, possibly with redirected input try: @@ -489,37 +520,86 @@ def get(required=False): return rv def send_get(what): - # Detect {\x00} pattern and convert to ctrl-key codes. - ctrl_code = lambda m: bytes([int(m.group(1))]) + # Detect hex {\x00} pattern and convert to ctrl-key codes. + ctrl_code = lambda m: bytes([int(m.group(1), 16)]) what = re.sub(rb"{\\x(\d\d)}", ctrl_code, what) os.write(master, what) return get() + def send_ctrl_c(): + # Send \x03 without trailing newline and wait for + # the full response (traceback + new prompt). + os.write(master, b"\x03") + return get(True) + with open(test_file, "rb") as f: - # instead of: output_mupy = subprocess.check_output(args, stdin=f) + # instead of: output_mupy = subprocess.check_output(cmdlist, stdin=f) master, slave = pty.openpty() - p = subprocess.Popen( - args, stdin=slave, stdout=slave, stderr=subprocess.STDOUT, bufsize=0 - ) - banner = get(True) - output_mupy = banner + b"".join(send_get(line) for line in f) - send_get(b"\x04") # exit the REPL, so coverage info is saved - # At this point the process might have exited already, but trying to - # kill it 'again' normally doesn't result in exceptions as Python and/or - # the OS seem to try to handle this nicely. When running Linux on WSL - # though, the situation differs and calling Popen.kill after the process - # terminated results in a ProcessLookupError. Just catch that one here - # since we just want the process to be gone and that's the case. try: - p.kill() - except ProcessLookupError: - pass - os.close(master) - os.close(slave) + preexec_fn = None + use_sigint_kill = False + # Tests with "# sigint:" need Ctrl-C (\x03) to + # generate SIGINT. MicroPython restores original + # terminal mode (ISIG on) during code execution, + # so on Linux we set up the PTY as a controlling + # terminal for proper signal delivery. On macOS, + # setsid/TIOCSCTTY breaks PTY I/O, so we fall + # back to os.kill(). + if send_sigint: + if sys.platform == "darwin": + use_sigint_kill = True + else: + import fcntl + import termios + + def preexec_fn(): + os.setsid() + fcntl.ioctl(0, termios.TIOCSCTTY, 0) + os.tcsetpgrp(0, os.getpid()) + + p = subprocess.Popen( + cmdlist, + stdin=slave, + stdout=slave, + stderr=subprocess.STDOUT, + bufsize=0, + preexec_fn=preexec_fn, + ) + banner = get(True) + if send_sigint: + import signal + + parts = [] + for line in f: + if b"{\\x03}" in line: + if use_sigint_kill: + os.kill(p.pid, signal.SIGINT) + parts.append(get(True)) + else: + parts.append(send_ctrl_c()) + else: + parts.append(send_get(line)) + output_mupy = banner + b"".join(parts) + else: + output_mupy = banner + b"".join(send_get(line) for line in f) + send_get(b"\x04") # exit the REPL, so coverage info is saved + # At this point the process might have exited already, but trying to + # kill it 'again' normally doesn't result in exceptions as Python and/or + # the OS seem to try to handle this nicely. When running Linux on WSL + # though, the situation differs and calling Popen.kill after the process + # terminated results in a ProcessLookupError. Just catch that one here + # since we just want the process to be gone and that's the case. + try: + p.kill() + except ProcessLookupError: + pass + finally: + os.close(master) + os.close(slave) else: output_mupy = subprocess.check_output( - args + [test_file], stderr=subprocess.STDOUT + cmdlist + [test_file], stderr=subprocess.STDOUT ) except subprocess.CalledProcessError: return b"CRASH" @@ -575,6 +655,10 @@ def send_get(what): # canonical form for all ports/platforms is to use \n for end-of-line output_mupy = normalize_newlines(output_mupy) + # for feature-check tests, return the output as-is + if is_feature_check: + return output_mupy + # don't try to convert the output if we should skip this test if had_crash or output_mupy in (b"SKIP\n", b"SKIP-TOO-LARGE\n", b"CRASH"): return output_mupy @@ -585,7 +669,12 @@ def send_get(what): if is_special or test_file_abspath in tests_with_regex_output: # convert parts of the output that are not stable across runs - with open(test_file + ".exp", "rb") as f: + # Prefer emitter-specific expected output. + exp_file = test_file + "." + args.emit + ".exp" + if not os.path.isfile(exp_file): + # Fall back to generic expected output. + exp_file = test_file + ".exp" + with open(exp_file, "rb") as f: lines_exp = [] for line in f.readlines(): if line == b"########\n": @@ -632,7 +721,9 @@ def run_feature_check(pyb, args, test_file): # REPL feature tests will not run via pyboard because they require prompt interactivity return b"" test_file_path = base_path("feature_check", test_file) - return run_micropython(pyb, args, test_file_path, test_file_path, is_special=True) + return run_micropython( + pyb, args, test_file_path, test_file_path, is_special=True, is_feature_check=True + ) class TestError(Exception): @@ -742,32 +833,15 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tstring = True if args.inlineasm_arch == "thumb": - # Check if @micropython.asm_thumb supports Thumb2 instructions, and skip such tests if it doesn't - output = run_feature_check(pyb, args, "inlineasm_thumb2.py") - if output != b"thumb2\n": - skip_tests.add("inlineasm/thumb/asmbcc.py") - skip_tests.add("inlineasm/thumb/asmbitops.py") - skip_tests.add("inlineasm/thumb/asmconst.py") - skip_tests.add("inlineasm/thumb/asmdiv.py") - skip_tests.add("inlineasm/thumb/asmit.py") - skip_tests.add("inlineasm/thumb/asmspecialregs.py") - if args.arch not in ("armv7emsp", "armv7emdp"): - skip_tests.add("inlineasm/thumb/asmfpaddsub.py") - skip_tests.add("inlineasm/thumb/asmfpcmp.py") - skip_tests.add("inlineasm/thumb/asmfpldrstr.py") - skip_tests.add("inlineasm/thumb/asmfpmuldiv.py") - skip_tests.add("inlineasm/thumb/asmfpsqrt.py") + for feature in ("thumb2", "vfp"): + if feature not in args.inlineasm_features: + for test in glob(f"inlineasm/thumb/asm_{feature}_*.py"): + skip_tests.add(test) if args.inlineasm_arch == "rv32": - # Discover extension-specific inlineasm tests and add them to the - # list of tests to run if applicable. for extension in RV32_ARCH_FLAGS: - try: - output = run_feature_check(pyb, args, "inlineasm_rv32_{}.py".format(extension)) - if output.strip() != "rv32_{}".format(extension).encode(): - skip_tests.add("inlineasm/rv32/asm_ext_{}.py".format(extension)) - except FileNotFoundError: - pass + if extension not in args.inlineasm_features: + skip_tests.add(f"inlineasm/rv32/asm_ext_{extension}.py") # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, "repl_emacs_check.py") @@ -787,7 +861,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): ) skip_endian = upy_byteorder != cpy_byteorder - skip_inlineasm = args.inlineasm_arch is None + skip_inlineasm = not args.inlineasm_arch # Some tests shouldn't be run on GitHub Actions if os.getenv("GITHUB_ACTIONS") == "true": @@ -815,6 +889,8 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("float/float_parse_doubleprec.py") if not args.unicode: + if args.via_mpy: + skip_tests.add("basics/string_escape.py") # stores a utf-8 character in the mpy file skip_tests.add("extmod/json_loads.py") # tests loading a utf-8 character # CIRCUITPY-CHANGE: asserts upstream's escaped Unicode repr, see py/objstrunicode.c @@ -862,6 +938,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # Skip platform-specific tests. skip_tests.update(platform_tests_to_skip.get(args.platform, ())) + # Skip error-reporting-specific tests. + skip_tests.update(error_reporting_tests_to_skip.get(args.error_reporting, ())) + # Some tests are known to fail on 64-bit machines if pyb is None and platform.architecture()[0] == "64bit": pass @@ -914,7 +993,7 @@ def run_one_test(test_file): is_slice = test_name.find("slice") != -1 is_async = test_name.startswith(("async_", "asyncio_")) or test_name.endswith("_async") is_const = test_name.startswith("const") - is_fstring = test_name.startswith("string_fstring") + is_fstring = test_name.startswith("string_fstring") or test_name.endswith("_fstring") is_tstring = test_name.startswith("string_tstring") or test_name.endswith("_tstring") is_inlineasm = test_name.startswith("asm") @@ -1076,7 +1155,7 @@ def run_one_test(test_file): # Print a note if this looks like it might have been a misfired unittest if not uses_unittest and not test_passed: - with open(test_file, "r") as f: + with open(test_file, "r", encoding="utf-8") as f: if any(re.match("^import.+unittest", l) for l in f.readlines()): print( "NOTE: {} may be a unittest that doesn't run unittest.main()".format( @@ -1155,6 +1234,9 @@ def main(): run-tests.py -e async -i async_foo - include all, exclude async, yet still include async_foo """, ) + cmd_parser.add_argument( + "-c", "--trace-output", action="store_true", help="trace test output while running" + ) cmd_parser.add_argument( "-t", "--test-instance", default="unix", help="the MicroPython instance to test" ) diff --git a/tests/stress/bytecode_limit.py b/tests/stress/bytecode_limit.py index 0a72b66fa05..7e77cdd5e5a 100644 --- a/tests/stress/bytecode_limit.py +++ b/tests/stress/bytecode_limit.py @@ -23,7 +23,7 @@ print("SKIP") raise SystemExit except RuntimeError as er: - results.append(repr(er)) + results.append("RuntimeError('{}')".format(str(er) or "bytecode overflow")) print(results) # Test changing size of code info (source line/bytecode mapping) due to changing diff --git a/tests/stress/bytecode_limit.py.exp b/tests/stress/bytecode_limit.py.exp index 50511665f00..3f1eb0db4c4 100644 --- a/tests/stress/bytecode_limit.py.exp +++ b/tests/stress/bytecode_limit.py.exp @@ -1,4 +1,4 @@ cond false cond false -["RuntimeError('bytecode overflow',)", "RuntimeError('bytecode overflow',)", 'ok', 'ok'] +["RuntimeError('bytecode overflow')", "RuntimeError('bytecode overflow')", 'ok', 'ok'] [123] diff --git a/tests/stress/qstr_limit.py b/tests/stress/qstr_limit.py index c7bd437f3ad..55b87075df9 100644 --- a/tests/stress/qstr_limit.py +++ b/tests/stress/qstr_limit.py @@ -13,7 +13,7 @@ def make_id(n, base="a"): try: exec(var + "=1", g) except RuntimeError as er: - print("RuntimeError", er, l) + print(str(er) or "name too long", l) continue print(var in g) @@ -27,7 +27,7 @@ def f(**k): try: exec("f({}=1)".format(make_id(l))) except RuntimeError as er: - print("RuntimeError", er, l) + print(str(er) or "name too long", l) # type construction for l in range(254, 259): @@ -35,7 +35,7 @@ def f(**k): try: print(type(id, (), {})) except RuntimeError as er: - print("RuntimeError", er, l) + print(str(er) or "name too long", l) # hasattr, setattr, getattr @@ -49,11 +49,11 @@ class A: try: setattr(a, id, 123) except RuntimeError as er: - print("RuntimeError", er, l) + print(str(er) or "name too long", l) try: print(hasattr(a, id), getattr(a, id)) except RuntimeError as er: - print("RuntimeError", er, l) + print(str(er) or "name too long", l) # format with keys for l in range(254, 259): @@ -61,7 +61,7 @@ class A: try: print(("{" + id + "}").format(**{id: l})) except RuntimeError as er: - print("RuntimeError", er, l) + print(str(er) or "name too long", l) # import module # (different OS's have different results so only run those that are consistent) @@ -71,7 +71,7 @@ class A: except ImportError: print("ok", l) except RuntimeError as er: - print("RuntimeError", er, l) + print(str(er) or "name too long", l) # import package for l in (100, 101, 102, 128, 129): @@ -80,4 +80,4 @@ class A: except ImportError: print("ok", l) except RuntimeError as er: - print("RuntimeError", er, l) + print(str(er) or "name too long", l) diff --git a/tests/stress/qstr_limit.py.exp b/tests/stress/qstr_limit.py.exp index 2349adf220f..5596b54bebd 100644 --- a/tests/stress/qstr_limit.py.exp +++ b/tests/stress/qstr_limit.py.exp @@ -1,38 +1,38 @@ True True -RuntimeError name too long 256 -RuntimeError name too long 257 -RuntimeError name too long 258 +name too long 256 +name too long 257 +name too long 258 {'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst': 1} {'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu': 1} -RuntimeError name too long 256 -RuntimeError name too long 257 -RuntimeError name too long 258 +name too long 256 +name too long 257 +name too long 258 -RuntimeError name too long 256 -RuntimeError name too long 257 -RuntimeError name too long 258 +name too long 256 +name too long 257 +name too long 258 True 123 True 123 -RuntimeError name too long 256 -RuntimeError name too long 256 -RuntimeError name too long 257 -RuntimeError name too long 257 -RuntimeError name too long 258 -RuntimeError name too long 258 +name too long 256 +name too long 256 +name too long 257 +name too long 257 +name too long 258 +name too long 258 254 255 -RuntimeError name too long 256 -RuntimeError name too long 257 -RuntimeError name too long 258 +name too long 256 +name too long 257 +name too long 258 ok 100 ok 101 -RuntimeError name too long 256 -RuntimeError name too long 257 -RuntimeError name too long 258 +name too long 256 +name too long 257 +name too long 258 ok 100 ok 101 ok 102 -RuntimeError name too long 128 -RuntimeError name too long 129 +name too long 128 +name too long 129 diff --git a/tests/stress/qstr_limit_str_modulo.py b/tests/stress/qstr_limit_str_modulo.py index 90b9f4364ec..c56f23b0a1d 100644 --- a/tests/stress/qstr_limit_str_modulo.py +++ b/tests/stress/qstr_limit_str_modulo.py @@ -18,4 +18,4 @@ def make_id(n, base="a"): try: print(("%(" + id + ")d") % {id: l}) except RuntimeError as er: - print("RuntimeError", er, l) + print("RuntimeError", str(er) or "name too long", l) diff --git a/tests/target_wiring/KIT_PSE84_AI.py b/tests/target_wiring/KIT_PSE84_AI.py new file mode 100644 index 00000000000..8ef3f08385d --- /dev/null +++ b/tests/target_wiring/KIT_PSE84_AI.py @@ -0,0 +1,5 @@ +# Target wiring for KIT_PSE84_AI. + +# UART(5) is on P17_1/P17_0. +uart_loopback_args = () +uart_loopback_kwargs = {"tx": "P17_1", "rx": "P17_0"} diff --git a/tests/target_wiring/alif.py b/tests/target_wiring/alif.py index 708b4591846..855c6e9ec90 100644 --- a/tests/target_wiring/alif.py +++ b/tests/target_wiring/alif.py @@ -9,3 +9,7 @@ spi_standalone_args_list = [(0,)] pwm_loopback_pins = [("P0_4", "P0_5")] + +# CAN args assume no connection for single device tests +can_args = (1,) +can_kwargs = {} diff --git a/tests/target_wiring/esp32.py b/tests/target_wiring/esp32.py index d94a6f60759..a707a171c8b 100644 --- a/tests/target_wiring/esp32.py +++ b/tests/target_wiring/esp32.py @@ -9,7 +9,7 @@ uart_loopback_args = (1,) uart_loopback_kwargs = {"tx": 4, "rx": 5} -if "ESP32C" in sys.implementation._machine: +if "ESP32-C" in sys.implementation._machine or "ESP32-H2" in sys.implementation._machine: spi_standalone_args_list = [(1,)] else: spi_standalone_args_list = [(1,), (2,)] diff --git a/tests/target_wiring/mimxrt.py b/tests/target_wiring/mimxrt.py index 2836d88ab9d..14190380e83 100644 --- a/tests/target_wiring/mimxrt.py +++ b/tests/target_wiring/mimxrt.py @@ -23,3 +23,7 @@ encoder_loopback_id = 0 encoder_loopback_out_pins = ("D0", "D2") encoder_loopback_in_pins = ("D1", "D3") + +# CAN args assume no connection for single device tests +can_args = (1,) +can_kwargs = {} diff --git a/tests/test_utils.py b/tests/test_utils.py index 7e43c4cae9f..40546b3cec2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -8,7 +8,6 @@ import re import subprocess import sys -import tempfile # See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0] # are guaranteed to always work, this one should though. @@ -224,34 +223,27 @@ def get_test_instance(test_instance, baudrate, user, password): return pyb -def prepare_script_for_target(args, *, script_text=None, force_plain=False): +def prepare_script_for_target(args, script_text, script_name, force_plain=False): if force_plain or (not args.via_mpy and args.emit == "bytecode"): # A plain test to run as-is, no processing needed. pass elif args.via_mpy: - tempname = tempfile.mktemp(dir="") - mpy_filename = tempname + ".mpy" - - script_filename = tempname + ".py" - with open(script_filename, "wb") as f: - f.write(script_text) - try: - subprocess.check_output( + # Compile the script with mpy-cross (using stdin/stdout). + p = subprocess.run( [MPYCROSS] + args.mpy_cross_flags.split() - + ["-o", mpy_filename, "-X", "emit=" + args.emit, script_filename], - stderr=subprocess.STDOUT, + + ["-s", script_name, "-X", "emit=" + args.emit, "--", "-"], + input=script_text, + capture_output=True, + check=True, ) + assert p.stderr == b"" + mpy_data = p.stdout except subprocess.CalledProcessError as er: - return True, b"mpy-cross crash\n" + er.output - - with open(mpy_filename, "rb") as f: - script_text = b"__buf=" + bytes(repr(f.read()), "ascii") + b"\n" - - rm_f(mpy_filename) - rm_f(script_filename) + return True, b"mpy-cross crash\n" + er.output + er.stderr + script_text = b"__buf=" + bytes(repr(mpy_data), "ascii") + b"\n" script_text += bytes(_injected_import_hook_code, "ascii") else: print("error: using emit={} must go via .mpy".format(args.emit)) @@ -274,29 +266,57 @@ def run_script_on_remote_target(pyb, args, test_file, is_special, requires_targe else: script = b"print('START TEST')\n" + script - had_crash, script = prepare_script_for_target(args, script_text=script, force_plain=is_special) + had_crash, script = prepare_script_for_target(args, script, test_file, force_plain=is_special) if had_crash: return True, script + # See if the output should be traced (printed to stdout), but not for feature_check tests. + trace_output = args.trace_output and "feature_check" not in test_file + if trace_output: + print(f"TRACE: {test_file}") + + # Function to collect output data as the test is run. + output_mupy = bytearray() + + def data_consumer(data): + if data == b"\x04": + # End of stream. + return + if trace_output: + # Print out the data as it's received. + sys.stdout.buffer.write(data) + sys.stdout.buffer.flush() + output_mupy.extend(data) + try: pyb.enter_raw_repl(timeout_overall=TEST_ENTER_RAW_REPL_TIMEOUT) + + # Inject target wiring if needed by the test. if requires_target_wiring and pyb.target_wiring_script: pyb.exec_( "import sys;sys.modules['target_wiring']=__build_class__(lambda:exec(" + repr(pyb.target_wiring_script) + "),'target_wiring')" ) - output_mupy = pyb.exec_(script, timeout=TEST_TIMEOUT) + + # Execute the test, and collect the output. + pyb.exec_(script, timeout=TEST_TIMEOUT, data_consumer=data_consumer) except pyboard.PyboardError as e: had_crash = True if not is_special and e.args[0] == "exception": - if prepend_start_test and e.args[1] == b"" and b"MemoryError" in e.args[2]: + no_output = len(output_mupy) == 0 + data_consumer(e.args[1]) + data_consumer(e.args[2]) + if prepend_start_test and no_output and b"MemoryError" in e.args[2]: output_mupy = b"SKIP-TOO-LARGE\n" else: - output_mupy = e.args[1] + e.args[2] + b"CRASH" + output_mupy += b"CRASH" else: - output_mupy = bytes(e.args[0], "ascii") + b"\nCRASH" + data_consumer(bytes(e.args[0], "ascii") + b"\n") + output_mupy += b"CRASH" + + output_mupy = bytes(output_mupy) if prepend_start_test: if output_mupy.startswith(b"START TEST\r\n"): diff --git a/tests/thread/thread_exc2.py.exp b/tests/thread/thread_exc2.py.exp index 469516dacc0..b7085c7af9b 100644 --- a/tests/thread/thread_exc2.py.exp +++ b/tests/thread/thread_exc2.py.exp @@ -1,5 +1,5 @@ Unhandled exception in thread started by Traceback (most recent call last): File \.\+, line 7, in thread_entry -ValueError: +ValueError: \$ done diff --git a/tests/thread/thread_exc2.py.native.exp b/tests/thread/thread_exc2.py.native.exp index 9b2e715ef8d..8188dd3ed00 100644 --- a/tests/thread/thread_exc2.py.native.exp +++ b/tests/thread/thread_exc2.py.native.exp @@ -1,3 +1,3 @@ Unhandled exception in thread started by -ValueError: +ValueError: \$ done diff --git a/tests/tools/manifest_c_module.py b/tests/tools/manifest_c_module.py new file mode 100644 index 00000000000..0dc89bd08e6 --- /dev/null +++ b/tests/tools/manifest_c_module.py @@ -0,0 +1,13 @@ +# Manifest for testing the c_module() build feature. +# +# Shared across ports; used in CI to exercise the c_module() manifest entry +# alongside per-port builds that also test the legacy USER_C_MODULES= +# command-line variable. + +# Include the port's default board manifest. +include("$(PORT_DIR)/boards/manifest.py") + +# Test user C modules via c_module(). +c_module("$(MPY_DIR)/examples/usercmodule/cexample") +c_module("$(MPY_DIR)/examples/usercmodule/cppexample") +c_module("$(MPY_DIR)/examples/usercmodule/subpackage") diff --git a/tests/tools/test_manifestfile.py b/tests/tools/test_manifestfile.py new file mode 100644 index 00000000000..c16f5142886 --- /dev/null +++ b/tests/tools/test_manifestfile.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# +# Unit tests for tools/manifestfile.py. Run with +# `python3 tests/tools/test_manifestfile.py`. + +import os +import sys +import tempfile +import unittest + +# Locate tools/manifestfile.py relative to this file. +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "tools")) +from manifestfile import ( + ManifestFile, + ManifestFileError, + MODE_COMPILE, + MODE_FREEZE, + MODE_PYPROJECT, +) + + +class TestCModule(unittest.TestCase): + def setUp(self): + self.tmpdir = tempfile.mkdtemp() + self.mf = ManifestFile(MODE_FREEZE, {"MPY_DIR": self.tmpdir}) + + def tearDown(self): + import shutil + + shutil.rmtree(self.tmpdir, ignore_errors=True) + + def _make_path(self, *parts): + path = os.path.join(self.tmpdir, *parts) + os.makedirs(os.path.dirname(path), exist_ok=True) + return path + + def test_missing_path(self): + with self.assertRaisesRegex(ManifestFileError, "C module path does not exist"): + self.mf.c_module(os.path.join(self.tmpdir, "no_such_dir")) + + def test_path_is_file_not_dir(self): + f = os.path.join(self.tmpdir, "regular_file") + open(f, "w").close() + with self.assertRaisesRegex(ManifestFileError, "C module path must be a directory"): + self.mf.c_module(f) + + def test_dir_missing_module_files(self): + d = os.path.join(self.tmpdir, "mymod") + os.makedirs(d) + with self.assertRaisesRegex( + ManifestFileError, "must contain micropython.mk or micropython.cmake" + ): + self.mf.c_module(d) + + def test_accepts_dir_with_mk(self): + d = os.path.join(self.tmpdir, "mymod_mk") + os.makedirs(d) + open(os.path.join(d, "micropython.mk"), "w").close() + self.mf.c_module(d) + self.assertIn(d, self.mf.c_modules()) + + def test_accepts_dir_with_cmake(self): + d = os.path.join(self.tmpdir, "mymod_cmake") + os.makedirs(d) + open(os.path.join(d, "micropython.cmake"), "w").close() + self.mf.c_module(d) + self.assertIn(d, self.mf.c_modules()) + + def test_path_var_substitution(self): + d = os.path.join(self.tmpdir, "submod") + os.makedirs(d) + open(os.path.join(d, "micropython.mk"), "w").close() + self.mf.c_module("$(MPY_DIR)/submod") + self.assertIn(d, self.mf.c_modules()) + + def test_unresolved_path_var(self): + with self.assertRaisesRegex( + ManifestFileError, r"Unresolved variable in c_module\(\) path" + ): + self.mf.c_module("$(BOARD_DIR)/nope") + + +class TestCModuleNonFreezeModes(unittest.TestCase): + # In MODE_COMPILE/MODE_PYPROJECT, c_module() is a silent no-op so a manifest + # using it can be evaluated in any mode without raising NameError. + + def setUp(self): + self.tmpdir = tempfile.mkdtemp() + + def tearDown(self): + import shutil + + shutil.rmtree(self.tmpdir, ignore_errors=True) + + def _no_op_in_mode(self, mode): + mf = ManifestFile(mode, {"MPY_DIR": self.tmpdir}) + # Path does not exist; would raise in MODE_FREEZE. + mf.c_module(os.path.join(self.tmpdir, "no_such_dir")) + self.assertEqual(mf.c_modules(), []) + + def test_compile_mode_noop(self): + self._no_op_in_mode(MODE_COMPILE) + + def test_pyproject_mode_noop(self): + self._no_op_in_mode(MODE_PYPROJECT) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unicode/ascii.py b/tests/unicode/ascii.py new file mode 100644 index 00000000000..2ae1f9fceed --- /dev/null +++ b/tests/unicode/ascii.py @@ -0,0 +1,22 @@ +# Test conversions from ASCII to/from UTF8 strings via constructor + +# Invalid ASCII characters via constructor +try: + str(b"ni\xe5\xa5\xbd", "ascii") # Valid utf8, invalid ascii +except UnicodeError: + print("UnicodeError") + +try: + str(b"\xff\xee\xff", "ascii") # Totally invalid +except UnicodeError: + print("UnicodeError") + + +bytes("😀", "utf8") +bytes("abcd", "ascii") + +# Conversion should fail if non-ASCII characters in string +try: + bytes("😀", "ascii") +except UnicodeError: + print("UnicodeError") diff --git a/tests/unicode/bytes_decode_encoding.py b/tests/unicode/bytes_decode_encoding.py new file mode 100644 index 00000000000..b20d51a167d --- /dev/null +++ b/tests/unicode/bytes_decode_encoding.py @@ -0,0 +1,75 @@ +# Test bytes.decode() and str.encode() with encoding parameter validation + +# Check if decode method is available (requires MICROPY_CPYTHON_COMPAT) +try: + b"".decode() +except AttributeError: + print("SKIP") + raise SystemExit + +# Test valid encodings for bytes.decode() +# utf-8 (default) +print(b"hello".decode("utf-8")) +print(b"hello".decode("utf8")) + +# ascii (subset of utf-8) +print(b"hello".decode("ascii")) + +# Test valid encoding for str.encode() +print("hello".encode("utf-8")) +print("hello".encode("utf8")) +print("hello".encode("ascii")) + +# Test with bytearray +print(bytearray(b"test").decode("utf-8")) + +# Test that UTF-8 still works correctly with non-ASCII characters +# © symbol (U+00A9) +print(b"\xc2\xa9".decode("utf-8")) +print("©".encode("utf-8")) + +# Test emoji 👍 (U+1F44D) +print(b"\xf0\x9f\x91\x8d".decode("utf-8")) +print("👍".encode("utf-8")) + +# Test ascii encode +print("abcde".encode("ascii")) + +# Test invalid decoded code points in repr fallback path. +print(repr(b"\xf4\x90\x80\x80".decode("utf-8"))) +print(repr(b"\xf5\x80\x80\x80".decode("utf-8"))) + +# Test invalid ASCII characters in decode +try: + print(repr(b"ni\xe5\xa5\xbd".decode("ascii"))) +except UnicodeError: + print("UnicodeError") + +# Test invalid encodings for bytes.decode() +# These should raise LookupError +invalid_encodings = ["latin-1", "latin1", "utf-16", "utf-32", "iso-8859-1", "cp1252"] + +for encoding in invalid_encodings: + try: + b"hello".decode(encoding) + print("UNEXPECTED:", encoding, "should raise LookupError") + except LookupError as e: + print("LookupError:", encoding) + +# Test bytes method accepting bytearray as argument (arg type normalization) +print(b"hello world".find(bytearray(b"world"))) +print(bytearray(b"hello world").find(bytearray(b"world"))) + +# Test invalid encodings for str.encode() +for encoding in invalid_encodings: + try: + "hello".encode(encoding) + print("UNEXPECTED:", encoding, "should raise LookupError") + except LookupError as e: + print("LookupError:", encoding) + +# Test invalid ascii characters in str.encode() +try: + "你好".encode("ascii") +except UnicodeError: + print("UnicodeError") diff --git a/tests/unicode/bytes_decode_encoding.py.exp b/tests/unicode/bytes_decode_encoding.py.exp new file mode 100644 index 00000000000..892ff019588 --- /dev/null +++ b/tests/unicode/bytes_decode_encoding.py.exp @@ -0,0 +1,30 @@ +hello +hello +hello +b'hello' +b'hello' +b'hello' +test +© +b'\xc2\xa9' +👍 +b'\xf0\x9f\x91\x8d' +b'abcde' +'\U00110000' +'\U00140000' +UnicodeError +LookupError: latin-1 +LookupError: latin1 +LookupError: utf-16 +LookupError: utf-32 +LookupError: iso-8859-1 +LookupError: cp1252 +6 +6 +LookupError: latin-1 +LookupError: latin1 +LookupError: utf-16 +LookupError: utf-32 +LookupError: iso-8859-1 +LookupError: cp1252 +UnicodeError diff --git a/tests/unicode/bytes_decode_ignore.py b/tests/unicode/bytes_decode_ignore.py new file mode 100644 index 00000000000..b42497c664d --- /dev/null +++ b/tests/unicode/bytes_decode_ignore.py @@ -0,0 +1,109 @@ +# Test bytes.decode() with error handler 'ignore' + +# Check if decode method is available (requires MICROPY_CPYTHON_COMPAT) +try: + b"".decode() +except AttributeError: + print("SKIP") + raise SystemExit + +# Check if error handlers are available (requires MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS) +# When feature is disabled, invalid UTF-8 raises LookupError even with 'ignore' +# When feature is enabled, invalid UTF-8 with 'ignore' returns a string +try: + result = b"\xff".decode("utf-8", "ignore") + # If we get here, feature is available +except (UnicodeError, LookupError): + # Feature not available - 'ignore' was ignored, strict mode was used + print("SKIP") + raise SystemExit + +# Test ignore mode with invalid UTF-8 +print(repr(b"\xff\xfe".decode("utf-8", "ignore"))) + +# Test strict mode (default) with invalid UTF-8 +try: + b"\xff\xfe".decode("utf-8") + print("UNEXPECTED") +except UnicodeError: + print("UnicodeError") + +# Test strict mode (explicit) with invalid UTF-8 +try: + b"\xff\xfe".decode("utf-8", "strict") + print("UNEXPECTED") +except UnicodeError: + print("UnicodeError") + +# Test with valid UTF-8 +print(repr(b"hello".decode("utf-8", "ignore"))) + +# Test valid UTF-8 with default mode +print(repr(b"hello".decode("utf-8"))) + +# Test mixed valid and invalid UTF-8 +print(repr(b"hello\xffworld".decode("utf-8", "ignore"))) + +# Test multiple invalid bytes +print(repr(b"\x80\x81\x82".decode("utf-8", "ignore"))) + +# Test invalid continuation byte +print(repr(b"\xc0\x20".decode("utf-8", "ignore"))) + +# Test incomplete sequence at end +print(repr(b"hello\xc0".decode("utf-8", "ignore"))) + +# Test valid multi-byte UTF-8 (© symbol) +print(repr(b"\xc2\xa9".decode("utf-8", "ignore"))) + +# Test bytearray support +print(repr(bytearray(b"\xff\xfe").decode("utf-8", "ignore"))) + +# Additional tests for continuation byte validation and incomplete sequences + +# Test 3-byte UTF-8 sequence - valid (e.g., U+4E00 - 一) +print(repr(b"\xe4\xb8\x80".decode("utf-8", "ignore"))) + +# Test 4-byte UTF-8 sequence - valid (e.g., U+1F600 - 😀) +print(repr(b"\xf0\x9f\x98\x80".decode("utf-8", "ignore"))) + +# Test incomplete 3-byte sequence (missing 2 continuation bytes) +print(repr(b"\xe4".decode("utf-8", "ignore"))) + +# Test incomplete 3-byte sequence (missing 1 continuation byte) +print(repr(b"\xe4\xb8".decode("utf-8", "ignore"))) + +# Test incomplete 4-byte sequence (missing 3 continuation bytes) +print(repr(b"\xf0".decode("utf-8", "ignore"))) + +# Test incomplete 4-byte sequence (missing 2 continuation bytes) +print(repr(b"\xf0\x9f".decode("utf-8", "ignore"))) + +# Test incomplete 4-byte sequence (missing 1 continuation byte) +print(repr(b"\xf0\x9f\x98".decode("utf-8", "ignore"))) + +# Test 3-byte sequence with invalid continuation byte (first byte invalid) +print(repr(b"\xe4\x20\x80".decode("utf-8", "ignore"))) + +# Test 3-byte sequence with invalid continuation byte (second byte invalid) +print(repr(b"\xe4\xb8\x20".decode("utf-8", "ignore"))) + +# Test 4-byte sequence with invalid continuation bytes +print(repr(b"\xf0\x20\x98\x80".decode("utf-8", "ignore"))) +print(repr(b"\xf0\x9f\x20\x80".decode("utf-8", "ignore"))) +print(repr(b"\xf0\x9f\x98\x20".decode("utf-8", "ignore"))) + +# Test mixed valid and incomplete sequences +print(repr(b"hello\xe4world".decode("utf-8", "ignore"))) +print(repr(b"hello\xf0world".decode("utf-8", "ignore"))) + +# Test valid multi-byte sequence mixed with invalid bytes (exercises got==need path) +print(repr(b"\xff\xc2\xa9".decode("utf-8", "ignore"))) # © preserved after invalid \xff +print(repr(b"\xff\xe4\xb8\x80".decode("utf-8", "ignore"))) # 一 preserved after invalid \xff + +# Test multiple incomplete sequences in a row +print(repr(b"\xe4\xf0\xe4".decode("utf-8", "ignore"))) + +# Test ignoring invalid ASCII +print(repr(b"\xe5\xa5\xbd".decode("ascii", "ignore"))) # valid utf8, not valid ascii +print(repr(b"a\xbb\xcc\xddef\x01".decode("ascii", "ignore"))) # fully invalid diff --git a/tests/unicode/bytes_decode_replace.py b/tests/unicode/bytes_decode_replace.py new file mode 100644 index 00000000000..36ba6551670 --- /dev/null +++ b/tests/unicode/bytes_decode_replace.py @@ -0,0 +1,130 @@ +# Test bytes.decode() with error handler 'replace' + +# Check if decode method is available (requires MICROPY_CPYTHON_COMPAT) +try: + b"".decode() +except AttributeError: + print("SKIP") + raise SystemExit + +# Check if error handlers are available (requires MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS) +# When feature is disabled, invalid UTF-8 raises UnicodeError even with 'replace' +# When feature is enabled, invalid UTF-8 with 'replace' returns a string +try: + result = b"\xff".decode("utf-8", "replace") + # If we get here, feature is available +except (UnicodeError, LookupError): + # Feature not available - 'replace' was ignored, strict mode was used + print("SKIP") + raise SystemExit + +# Test replace mode with invalid UTF-8 +print(repr(b"\xff\xfe".decode("utf-8", "replace"))) + +# Test strict mode (default) with invalid UTF-8 +try: + b"\xff\xfe".decode("utf-8") + print("UNEXPECTED") +except UnicodeError: + print("UnicodeError") + +# Test strict mode (explicit) with invalid UTF-8 +try: + b"\xff\xfe".decode("utf-8", "strict") + print("UNEXPECTED") +except UnicodeError: + print("UnicodeError") + +# Test with valid UTF-8 +print(repr(b"hello".decode("utf-8", "replace"))) + +# Test valid UTF-8 with default mode +print(repr(b"hello".decode("utf-8"))) + +# Test mixed valid and invalid UTF-8 +print(repr(b"hello\xffworld".decode("utf-8", "replace"))) + +# Test multiple invalid bytes +print(repr(b"\x80\x81\x82".decode("utf-8", "replace"))) + +# Test invalid continuation byte +print(repr(b"\xc0\x20".decode("utf-8", "replace"))) + +# Test incomplete sequence at end +print(repr(b"hello\xc0".decode("utf-8", "replace"))) + +# Test valid multi-byte UTF-8 (© symbol) +print(repr(b"\xc2\xa9".decode("utf-8", "replace"))) + +# Test bytearray support +print(repr(bytearray(b"\xff\xfe").decode("utf-8", "replace"))) + +# Test replace mode - should either work or raise NotImplementedError +try: + result = b"\xff\xfe".decode("utf-8", "replace") + print(repr(result)) +except LookupError: + print("LookupError") + +# Test replace with valid UTF-8 +try: + result = b"hello".decode("utf-8", "replace") + print(repr(result)) +except LookupError: + print("LookupError") + +# Test replace with mixed content +try: + result = b"hello\xffworld".decode("utf-8", "replace") + print(repr(result)) +except LookupError: + print("LookupError") + +# Additional tests for continuation byte validation and incomplete sequences + +# Test 3-byte UTF-8 sequence - valid (e.g., U+4E00 - 一) +print(repr(b"\xe4\xb8\x80".decode("utf-8", "replace"))) + +# Test 4-byte UTF-8 sequence - valid (e.g., U+1F600 - 😀) +print(repr(b"\xf0\x9f\x98\x80".decode("utf-8", "replace"))) + +# Test valid multi-byte sequence mixed with invalid bytes (exercises got==need path) +print(repr(b"\xff\xc2\xa9".decode("utf-8", "replace"))) # \ufffd + © after invalid \xff +print(repr(b"\xff\xe4\xb8\x80".decode("utf-8", "replace"))) # \ufffd + 一 after invalid \xff + +# Test incomplete 3-byte sequence (missing 2 continuation bytes) +print(repr(b"\xe4".decode("utf-8", "replace"))) + +# Test incomplete 3-byte sequence (missing 1 continuation byte) +print(repr(b"\xe4\xb8".decode("utf-8", "replace"))) + +# Test incomplete 4-byte sequence (missing 3 continuation bytes) +print(repr(b"\xf0".decode("utf-8", "replace"))) + +# Test incomplete 4-byte sequence (missing 2 continuation bytes) +print(repr(b"\xf0\x9f".decode("utf-8", "replace"))) + +# Test incomplete 4-byte sequence (missing 1 continuation byte) +print(repr(b"\xf0\x9f\x98".decode("utf-8", "replace"))) + +# Test 3-byte sequence with invalid continuation byte (first byte invalid) +print(repr(b"\xe4\x20\x80".decode("utf-8", "replace"))) + +# Test 3-byte sequence with invalid continuation byte (second byte invalid) +print(repr(b"\xe4\xb8\x20".decode("utf-8", "replace"))) + +# Test 4-byte sequence with invalid continuation bytes +print(repr(b"\xf0\x20\x98\x80".decode("utf-8", "replace"))) +print(repr(b"\xf0\x9f\x20\x80".decode("utf-8", "replace"))) +print(repr(b"\xf0\x9f\x98\x20".decode("utf-8", "replace"))) + +# Test mixed valid and incomplete sequences +print(repr(b"hello\xe4world".decode("utf-8", "replace"))) +print(repr(b"hello\xf0world".decode("utf-8", "replace"))) + +# Test multiple incomplete sequences in a row +print(repr(b"\xe4\xf0\xe4".decode("utf-8", "replace"))) + +# Test replacing invalid ASCII +print(repr(b"\xe5\xa5\xbd".decode("ascii", "replace"))) # valid utf8, not valid ascii +print(repr(b"a\xbb\xcc\xddef\x01".decode("ascii", "replace"))) # fully invalid diff --git a/tests/unicode/str_center.py b/tests/unicode/str_center.py new file mode 100644 index 00000000000..226074df4b7 --- /dev/null +++ b/tests/unicode/str_center.py @@ -0,0 +1,36 @@ +# Test str.center() with Unicode characters +# Issue #17827 + +try: + str.center +except AttributeError: + print("SKIP") + raise SystemExit + +# ASCII baseline +print("hello".center(10)) + +# Latin with accent (é is 2 bytes in UTF-8) +print("héllo".center(10)) + +# Chinese (each char is 3 bytes in UTF-8) +print("你好".center(10)) + +# Emoji (4 bytes in UTF-8) +print("🎉".center(5)) + +# German with umlaut +print("München".center(15)) + +# Cyrillic +print("Москва".center(12)) + +# Edge cases +print("test".center(4)) # Exact fit +print("test".center(3)) # String longer than width +print("x".center(1)) # Single char, exact fit +print("".center(5)) # Empty string + +# Mixed ASCII and Unicode +print("café".center(10)) +print("hello世界".center(12)) diff --git a/tests/unicode/str_from_buffer_errors.py b/tests/unicode/str_from_buffer_errors.py new file mode 100644 index 00000000000..64008738a1e --- /dev/null +++ b/tests/unicode/str_from_buffer_errors.py @@ -0,0 +1,39 @@ +# str(buffer, encoding, errors) should apply the 'ignore'/'replace' error +# handlers to any buffer source (array.array, memoryview, ...), the same way it +# does for bytes and bytearray. Companion to str_from_buffer_snapshot.py. + +try: + import array + + memoryview +except (ImportError, NameError): + print("SKIP") + raise SystemExit + +# Requires the decode error handlers (MICROPY_PY_BUILTINS_BYTES_DECODE_ERRORS). +# When the feature is disabled, invalid UTF-8 raises even with 'replace'. +try: + str(memoryview(b"\xff"), "utf-8", "replace") +except UnicodeError: + print("SKIP") + raise SystemExit + +# array.array holding invalid UTF-8 bytes (typecode "B" keeps this endian-neutral). +a = array.array("B", b"a\xffb\x80c") +print(repr(str(a, "utf-8", "replace"))) +print(repr(str(a, "utf-8", "ignore"))) + +# A memoryview over invalid UTF-8 (0xc3 0x28 is an invalid 2-byte sequence). +mv = memoryview(b"x\xc3\x28y") +print(repr(str(mv, "utf-8", "replace"))) +print(repr(str(mv, "utf-8", "ignore"))) + +# Strict decoding (the default) must still raise for a buffer source. +try: + str(memoryview(b"a\xffb"), "utf-8") + print("UNEXPECTED") +except UnicodeError: + print("UnicodeError") + +# A valid-UTF-8 buffer passes through an error handler unchanged ("café"). +print(repr(str(array.array("B", b"caf\xc3\xa9"), "utf-8", "replace"))) # codespell:ignore caf diff --git a/tests/unicode/str_from_buffer_snapshot.py b/tests/unicode/str_from_buffer_snapshot.py new file mode 100644 index 00000000000..c4e0a871471 --- /dev/null +++ b/tests/unicode/str_from_buffer_snapshot.py @@ -0,0 +1,30 @@ +# Companion to str_from_bytearray.py: str()/bytes() built from other mutable +# buffer sources (array.array and a writable memoryview) must also be independent +# snapshots, not views onto the source buffer. +try: + import array + + memoryview +except (ImportError, NameError): + print("SKIP") + raise SystemExit + +# Non-interned byte-literal payloads (with raw UTF-8 sequences) avoid relying on +# str.encode() to build the test data. + +# array.array is a mutable buffer source; str()/bytes() must snapshot the data. +# Content decodes to "array ünicode 🐍 target!". +a = array.array("B", b"array \xc3\xbcnicode \xf0\x9f\x90\x8d target!") +s = str(a, "utf-8") +b = bytes(a) +a[0] = ord("X") +print(s) +print(b) + +# A writable memoryview (here over an array) is also a mutable source. +# Content decodes to "memoryview över 🐍 python!". +a = array.array("B", b"memoryview \xc3\xb6ver \xf0\x9f\x90\x8d python!") +mv = memoryview(a) +s = str(mv, "utf-8") +a[0] = ord("X") +print(s) diff --git a/tests/unicode/str_from_bytearray.py b/tests/unicode/str_from_bytearray.py new file mode 100644 index 00000000000..12744da5c47 --- /dev/null +++ b/tests/unicode/str_from_bytearray.py @@ -0,0 +1,50 @@ +# Test that a str/bytes created from a bytearray is an independent snapshot, +# and not a view onto the (mutable) bytearray buffer. Mutating or resizing the +# source bytearray afterwards must not change the previously created object. + +# Skip if needed via `skip_bytearray` logic in run-tests.py + +# Non-interned payloads (byte literals) are used so the result isn't returned as +# an existing qstr. Every scenario is covered with plain-ASCII data so the core +# bytearray paths are exercised directly; a couple of cases embed raw UTF-8 byte +# sequences to also cover multi-byte Unicode decoding, without relying on +# str.encode() to build the test data. + +# str(bytearray, ...) then mutate the source in place. +ba = bytearray(b"the quick brown fox jumped over!") +s = str(ba, "utf-8") +ba[0] = ord("T") +print(s) +print(s[0]) + +# Same, but with multi-byte UTF-8 content: "café naïve 🐍 snapshot!". +ba = bytearray(b"caf\xc3\xa9 na\xc3\xafve \xf0\x9f\x90\x8d snapshot!") # codespell:ignore caf +s = str(ba, "utf-8") +ba[0] = ord("X") +print(s) + +# Overwriting every byte of the source must not change the str. +ba = bytearray(b"snapshot test one two three four!") +s = str(ba, "utf-8") +for i in range(len(ba)): + ba[i] = ord("x") +print(s) + +# Growing the bytearray reallocates its buffer; the str must stay intact. +ba = bytearray(b"grow test alpha bravo charlie delta!") +s = str(ba, "utf-8") +for _ in range(1000): + ba.append(ord("Z")) +print(s) + +# bytes(bytearray) is likewise an independent snapshot. +ba = bytearray(b"bytes snapshot alpha bravo charlie!") +b = bytes(ba) +ba[0] = ord("X") +print(b) + +# Same, but with multi-byte UTF-8 content: "bytes ünicode 🐍 snakes!". +ba = bytearray(b"bytes \xc3\xbcnicode \xf0\x9f\x90\x8d snakes!") +b = bytes(ba) +ba[0] = ord("X") +print(b) diff --git a/tests/unicode/unicode.py b/tests/unicode/unicode.py index 58d406e63eb..97f0599ab01 100644 --- a/tests/unicode/unicode.py +++ b/tests/unicode/unicode.py @@ -17,11 +17,6 @@ enc = s.encode() print(enc, enc.decode() == s) -# printing of unicode chars using repr -# NOTE: for some characters (eg \u10ff) we differ to CPython -print(repr("a\uffff")) -print(repr("a\U0001ffff")) - # test invalid escape code try: eval('"\\U00110000"') @@ -51,3 +46,13 @@ str(b"\xf0\xe0\xed\xe8", "utf8") except UnicodeError: print("UnicodeError") + +# test surrogate repr uses \uXXXX escape +print(repr(chr(0xD800))) + +# test str() from buffer-protocol object (memoryview) +print(str(memoryview(b"hello"), "utf-8")) +try: + str(memoryview(b"\xff"), "utf-8") +except UnicodeError: + print("UnicodeError") diff --git a/tests/unicode/unicode_char_format.py b/tests/unicode/unicode_char_format.py new file mode 100644 index 00000000000..3d56ae92afa --- /dev/null +++ b/tests/unicode/unicode_char_format.py @@ -0,0 +1,52 @@ +# test %c formatting with unicode characters (issue #3364) +# tests that character codes >= 128 are properly encoded as UTF-8 + +print("%c%c" % (0x3BC, 0x1F40D)) # Greek letter mu and snake emoji + +# ASCII character +print("%c" % 65) + +# 2-byte UTF-8 characters +print("%c" % 128) +print("%c" % 169) # copyright symbol © +print("%c" % 255) + +# 3-byte UTF-8 character +print("%c" % 0x4E00) # CJK ideograph 一 + +# 4-byte UTF-8 character +print("%c" % 0x1F600) # emoji 😀 + +# test with .format() method +print("{:c}".format(169)) +print("{:c}".format(0x4E00)) +print("{:c}{:c}".format(0x3BC, 0x1F40D)) + +# Test boundary values - valid maximum unicode codepoint +print("%c" % 0x10FFFF) # Last valid unicode codepoint + +# Test invalid codepoint - >= 0x110000 should raise OverflowError +try: + print("%c" % 0x110000) + print("UNEXPECTED: should have raised OverflowError") +except OverflowError: + print("OverflowError") + +try: + print("%c" % 0x110001) + print("UNEXPECTED: should have raised OverflowError") +except OverflowError: + print("OverflowError") + +# Test format() method with invalid codepoint +try: + print("{:c}".format(0x110000)) + print("UNEXPECTED: should have raised OverflowError") +except OverflowError: + print("OverflowError") + +try: + print("{:c}".format(0x200000)) + print("UNEXPECTED: should have raised OverflowError") +except OverflowError: + print("OverflowError") diff --git a/tests/unicode/unicode_char_format_fstring.py b/tests/unicode/unicode_char_format_fstring.py new file mode 100644 index 00000000000..9dac1052e07 --- /dev/null +++ b/tests/unicode/unicode_char_format_fstring.py @@ -0,0 +1,6 @@ +# test %c formatting with unicode characters in f-strings + +c = 169 +print(f"{c:c}") +c = 0x1F600 +print(f"{c:c}") diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index cdb11455694..d474af62ea0 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -33,6 +33,8 @@ abc # GC 0 0 +1 +1 # GC part 2 pass # tracked allocation @@ -45,6 +47,7 @@ m_tracked_head = 0 5 1 6 1 7 1 +1 0 1 1 1 2 1 @@ -54,17 +57,24 @@ m_tracked_head = 0 6 1 7 1 m_tracked_head = 0 +# tracked realloc +grow preserves data: 1 +shrink preserves data: 1 +realloc gc stable: 1 +realloc(NULL) ok: 1 +realloc(ptr, 0) returns NULL: 1 +m_tracked_head after cleanup: 0 # vstr tests sts test tes -RuntimeError: -RuntimeError: +RuntimeError: \$ +RuntimeError: \$ # repl ame__ -port +port \$ builtins micropython __future__ _asyncio _thread aesio array audiocore @@ -117,6 +127,13 @@ deadbeef 0deadbeef c0ffee 000c0ffee +# list argument helpers +TypeError: \$ +ValueError: \$ +mp_obj_list_ensure same list? 1 +mp_obj_list_optional_arg same list? 1 +mp_obj_list_optional_arg new list len 3 +mp_obj_list_optional_arg new list from NULL len 3 # runtime utils TypeError: unsupported type for __abs__: 'str' TypeError: unsupported types for __divmod__: 'str', 'str' @@ -127,11 +144,8 @@ OverflowError: overflow converting long int to machine word OverflowError: overflow converting long int to machine word TypeError: can't convert NoneType to int TypeError: can't convert NoneType to int -ValueError: +ValueError: \$ Warning: test -# binary -123 -456 # VM 2 1 # scheduler @@ -145,8 +159,8 @@ unlocked 1 2 3 -KeyboardInterrupt: -KeyboardInterrupt: +KeyboardInterrupt: \$ +KeyboardInterrupt: \$ 10 loop scheduled function diff --git a/tests/unix/ffi_callback.py b/tests/unix/ffi_callback.py index 21bfccf251e..b5dfe28b644 100644 --- a/tests/unix/ffi_callback.py +++ b/tests/unix/ffi_callback.py @@ -16,7 +16,7 @@ def ffi_open(names): raise err -libc = ffi_open(("libc.so", "libc.so.0", "libc.so.6", "libc.dylib")) +libc = ffi_open(("libc.so", "libc.so.0", "libc.so.6", "libc.so.7", "libc.dylib")) qsort = libc.func("v", "qsort", "piip") diff --git a/tests/unix/ffi_float.py b/tests/unix/ffi_float.py index 03bd9f7f17b..117db69dd6c 100644 --- a/tests/unix/ffi_float.py +++ b/tests/unix/ffi_float.py @@ -17,7 +17,7 @@ def ffi_open(names): raise err -libc = ffi_open(("libc.so", "libc.so.0", "libc.so.6", "libc.dylib")) +libc = ffi_open(("libc.so", "libc.so.0", "libc.so.6", "libc.so.7", "libc.dylib")) try: strtof = libc.func("f", "strtof", "sp") @@ -33,7 +33,9 @@ def ffi_open(names): print("%.6f" % strtod("1.23", None)) # test passing double and float args -libm = ffi_open(("libm.so", "libm.so.6", "libc.so.0", "libc.so.6", "libc.dylib")) +libm = ffi_open( + ("libm.so", "libm.so.5", "libm.so.6", "libc.so.0", "libc.so.6", "libc.so.7", "libc.dylib") +) tgamma = libm.func("d", "tgamma", "d") for fun_name in ("tgamma",): fun = globals()[fun_name] diff --git a/tools/ar_util.py b/tools/ar_util.py index b90d3790314..4eb41c93654 100644 --- a/tools/ar_util.py +++ b/tools/ar_util.py @@ -39,6 +39,10 @@ Archive = None +DEFAULT_CACHE_BASE_PATH = ".mpy_ld_cache" +DEFAULT_CACHE_PREFIX = "ar_" + + class PickleCache: def __init__(self, path, prefix=""): self.path = path @@ -63,11 +67,21 @@ def load(self, key): return pickle.load(f) -def cached(key, cache): +PICKLE_CACHE = None + + +def init_cache(path, prefix): + global PICKLE_CACHE + PICKLE_CACHE = PickleCache(path, prefix) + + +def cached(key, provider): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): cache_key = key(*args, **kwargs) + cache = provider() + assert cache is not None try: d = cache.load(cache_key) if d["key"] != cache_key: @@ -114,7 +128,7 @@ def _cache_key(self): sha.update(bytes.fromhex("00000000000000000000000000000001")) return sha.hexdigest() - @cached(key=_cache_key, cache=PickleCache(path=".mpy_ld_cache", prefix="ar_")) + @cached(key=_cache_key, provider=lambda: PICKLE_CACHE) def load_symbols(self): print("Loading", self.fn) objs = defaultdict(lambda: {"def": set(), "undef": set(), "weak": set()}) diff --git a/tools/boardgen.py b/tools/boardgen.py index 3723e7ce31b..6b295f484f3 100644 --- a/tools/boardgen.py +++ b/tools/boardgen.py @@ -475,6 +475,10 @@ def load_inputs(self, out_source): def generate_extra_files(self): pass + def print_pin_source(self, out_source): + for pin in self.available_pins(): + pin.print_source(out_source) + def main(self): parser = argparse.ArgumentParser(description="Generate board specific pin file") parser.add_argument("--board-csv") @@ -495,8 +499,7 @@ def main(self): self.load_inputs(out_source) # Allow a port to print arbitrary per-pin content. - for pin in self.available_pins(): - pin.print_source(out_source) + self.print_pin_source(out_source) # Print the tables and dictionaries. self.print_source(out_source) diff --git a/tools/build_release_files.py b/tools/build_release_files.py index b65b2aeb723..4394cf931d9 100755 --- a/tools/build_release_files.py +++ b/tools/build_release_files.py @@ -70,6 +70,29 @@ def flash_usage(port, build_dir): return None, None +def record_size(port, board, language, used, region, status, source): + """Record one language build's flash usage in sizes/.json. + + CI uploads the file as its own small artifact so a later job can gather every + board's sizes without scraping logs. `source` is "measured" for a linked build and + "predicted" for a language skipped by the size prediction; `used` is None, written as + null, when the build failed or the port does not report its flash region. + """ + os.makedirs("../sizes", exist_ok=True) + path = f"../sizes/{board}.json" + try: + with open(path, "r") as f: + record = json.load(f) + except FileNotFoundError: + record = {"port": port, "board": board, "region": None, "languages": {}} + if region is not None: + record["region"] = region + record["languages"][language] = {"used": used, "status": status, "source": source} + with open(path, "w") as f: + json.dump(record, f, indent=1) + f.write("\n") + + def c_array_bytes(path): """Sum the bytes of the const arrays initialised in a generated C file.""" if not path.exists(): @@ -218,6 +241,15 @@ def generate_translation(port, board, build_dir, language): flush=True, ) if skip: + record_size( + board_info["port"], + board, + language, + predicted_flash, + flash_region, + "skipped", + "predicted", + ) continue make_result = subprocess.run( @@ -275,29 +307,43 @@ def generate_translation(port, board, build_dir, language): print(make_result.stdout.decode("utf-8")) print(other_output) - if predicted_flash is not None and make_result.returncode == 0: - actual_flash, _ = flash_usage(board_info["port"], build_dir) - if actual_flash is not None: - print( - "Flash size check {board} {language}: predicted {predicted}," - " actual {actual}, error {error:+d}".format( - board=board, - language=language, - predicted=predicted_flash, - actual=actual_flash, - error=predicted_flash - actual_flash, - ) + # Languages share a build directory, so a failed link leaves the previous + # language's firmware.size.json behind; only trust it after a successful build. + actual_flash, actual_region = None, None + if make_result.returncode == 0: + actual_flash, actual_region = flash_usage(board_info["port"], build_dir) + record_size( + board_info["port"], + board, + language, + actual_flash, + actual_region, + "succeeded" if make_result.returncode == 0 else "failed", + "measured", + ) + + if predicted_flash is not None and actual_flash is not None: + print( + "Flash size check {board} {language}: predicted {predicted}," + " actual {actual}, error {error:+d}".format( + board=board, + language=language, + predicted=predicted_flash, + actual=actual_flash, + error=predicted_flash - actual_flash, ) + ) # Flush so we will see something before 10 minutes has passed. print(flush=True) + if extensions == ["exe"] and language == LANGUAGE_FIRST and exit_status == 0: + # The board builds a host executable for testing. There is no flash for a + # translation to overflow, and nobody downloads a translated simulator. + print("Skipping languages") + break + if (not build_all) and (language == LANGUAGE_FIRST) and (exit_status == 0): - if extensions == ["exe"]: - # The board builds a host executable, so there is no flash for a - # translation to overflow and nothing for the other 16 to prove. - print("Skipping languages") - break used_flash, flash_region = flash_usage(board_info["port"], build_dir) if used_flash is None: print("Flash usage unknown, building all languages") diff --git a/tools/ci.sh b/tools/ci.sh index f1edf936435..c01a8d4b928 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -29,6 +29,12 @@ function ci_gcc_riscv_setup { riscv64-unknown-elf-gcc --version } +function ci_gcc_ppc64_setup { + sudo apt-get update + sudo apt-get install gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross + powerpc64le-linux-gnu-gcc --version +} + function ci_picotool_setup { # Manually installing picotool ensures we use a release version, and speeds up the build. git clone https://github.com/raspberrypi/pico-sdk.git @@ -42,8 +48,7 @@ function ci_picotool_setup { # c code formatting function ci_c_code_formatting_setup { - sudo apt-get update - sudo apt-get install uncrustify + pip install micropython-uncrustify==1.0.0.post1 uncrustify --version } @@ -91,7 +96,7 @@ function ci_code_size_build { # Override the list by setting PORTS_TO_CHECK in the environment before invoking ci. : ${PORTS_TO_CHECK:=bmus3xpdv} - SUBMODULES="lib/asf4 lib/berkeley-db-1.xx lib/btstack lib/cyw43-driver lib/lwip lib/mbedtls lib/micropython-lib lib/nxp_driver lib/pico-sdk lib/stm32lib lib/tinyusb" + SUBMODULES="lib/CMSIS_5 lib/CMSIS_6 lib/asf4 lib/berkeley-db-1.xx lib/btstack lib/cyw43-driver lib/lwip lib/mbedtls lib/micropython-lib lib/nxp_driver lib/pico-sdk lib/stm32lib lib/tinyusb" # Default GitHub pull request sets HEAD to a generated merge commit # between PR branch (HEAD^2) and base branch (i.e. master) (HEAD^1). @@ -158,7 +163,7 @@ function ci_mpy_format_setup { function ci_mpy_format_test { # Test mpy-tool.py dump feature on bytecode - python3 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy + python3 ./tools/mpy-tool.py -xd tests/assets/frozentest.mpy # Build MicroPython ci_unix_standard_build @@ -167,7 +172,7 @@ function ci_mpy_format_test { export MICROPYPATH=. # Test mpy-tool.py running under MicroPython - $micropython ./tools/mpy-tool.py -x -d tests/frozen/frozentest.mpy + $micropython ./tools/mpy-tool.py -x -d tests/assets/frozentest.mpy # Test mpy-tool.py dump feature on native code make -C examples/natmod/features1 @@ -184,6 +189,21 @@ function ci_mpy_cross_debug_emitter { grep -E "ENTRY|EXIT" | wc -l | grep "^2$" } +######################################################################################## +# ports/alif + +function ci_alif_setup { + ci_gcc_arm_setup +} + +function ci_alif_ae3_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_HP submodules + make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_HE submodules + make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_DUAL + make ${MAKEOPTS} -C ports/alif BOARD=ALIF_ENSEMBLE MCU_CORE=M55_DUAL USER_C_MODULES=../../examples/usercmodule +} + ######################################################################################## # ports/cc3200 @@ -197,18 +217,22 @@ function ci_cc3200_build { } ######################################################################################## -# ports/esp32 +# ports/embed -# GitHub tag of ESP-IDF to use for CI, extracted from the esp32 dependency lockfile -# This should end up as a tag name like vX.Y.Z -# (note: This hacky parsing can be replaced with 'yq' once Ubuntu >=24.04 is in use) -IDF_VER=v$(grep -A10 "idf:" ports/esp32/lockfiles/dependencies.lock.esp32 | grep "version:" | head -n1 | sed -E 's/ +version: //') -PYTHON=$(command -v python3 2> /dev/null) -PYTHON_VER=$(${PYTHON:-python} --version | cut -d' ' -f2) +function ci_embedding_build { + make ${MAKEOPTS} -C examples/embedding -f micropython_embed.mk + make ${MAKEOPTS} -C examples/embedding + ./examples/embedding/embed | grep "hello world" +} -export IDF_CCACHE_ENABLE=1 +######################################################################################## +# ports/esp32 function ci_esp32_idf_setup { + if [ -z "$IDF_VER" ]; then + echo "IDF_VER environment variable must be set before running" + return 1 + fi echo "Using ESP-IDF version $IDF_VER" git clone --depth 1 --branch $IDF_VER https://github.com/espressif/esp-idf.git # doing a treeless clone isn't quite as good as --shallow-submodules, but it @@ -227,23 +251,29 @@ function ci_esp32_build_common { make ${MAKEOPTS} -C ports/esp32 submodules } -function ci_esp32_build_cmod_spiram_s2 { +function ci_esp32_build_cmod_spiram_d2wd { ci_esp32_build_common + # Combined USER_C_MODULES + freeze manifest test on ESP32_GENERIC. make ${MAKEOPTS} -C ports/esp32 \ USER_C_MODULES=../../../examples/usercmodule/micropython.cmake \ - FROZEN_MANIFEST=$(pwd)/ports/esp32/boards/manifest_test.py + FROZEN_MANIFEST="$(pwd)/ports/esp32/boards/manifest_test.py" # Test building native .mpy with xtensawin architecture. ci_native_mpy_modules_build xtensawin - make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM - make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_S2 + # Test the c_module() codepath on the SPIRAM variant. + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM \ + FROZEN_MANIFEST="$(pwd)/tests/tools/manifest_c_module.py" + + # D2WD is the variant with smallest application partition in flash + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC BOARD_VARIANT=D2WD } -function ci_esp32_build_s3_c3 { +function ci_esp32_build_s2_s3_c3 { ci_esp32_build_common + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_S2 make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_S3 make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C3 } @@ -256,9 +286,10 @@ function ci_esp32_build_c2_c5_c6 { make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_C6 } -function ci_esp32_build_p4 { +function ci_esp32_build_h2_p4 { ci_esp32_build_common + make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_H2 make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_P4 make ${MAKEOPTS} -C ports/esp32 BOARD=ESP32_GENERIC_P4 BOARD_VARIANT=C6_WIFI } @@ -283,31 +314,12 @@ function ci_esp8266_build { make ${MAKEOPTS} -C ports/esp8266 submodules make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC BOARD_VARIANT=FLASH_512K - make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC BOARD_VARIANT=FLASH_1M + make ${MAKEOPTS} -C ports/esp8266 BOARD=ESP8266_GENERIC BOARD_VARIANT=FLASH_1M USER_C_MODULES=../../examples/usercmodule # Test building native .mpy with xtensa architecture. ci_native_mpy_modules_build xtensa } -######################################################################################## -# ports/webassembly - -function ci_webassembly_setup { - npm install terser - git clone https://github.com/emscripten-core/emsdk.git - (cd emsdk && ./emsdk install latest && ./emsdk activate latest) -} - -function ci_webassembly_build { - source emsdk/emsdk_env.sh - make ${MAKEOPTS} -C ports/webassembly VARIANT=pyscript submodules - make ${MAKEOPTS} -C ports/webassembly VARIANT=pyscript -} - -function ci_webassembly_run_tests { - make -C ports/webassembly VARIANT=pyscript test_min -} - ######################################################################################## # ports/mimxrt @@ -320,7 +332,7 @@ function ci_mimxrt_build { make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1020_EVK submodules make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1020_EVK make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 submodules - make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 + make ${MAKEOPTS} -C ports/mimxrt BOARD=TEENSY40 USER_C_MODULES=../../examples/usercmodule make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1060_EVK submodules make ${MAKEOPTS} -C ports/mimxrt BOARD=MIMXRT1060_EVK CFLAGS_EXTRA=-DMICROPY_HW_USB_MSC=1 } @@ -337,22 +349,24 @@ function ci_nrf_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/nrf submodules make ${MAKEOPTS} -C ports/nrf BOARD=PCA10040 - make ${MAKEOPTS} -C ports/nrf BOARD=MICROBIT + make ${MAKEOPTS} -C ports/nrf BOARD=MICROBIT USER_C_MODULES=../../examples/usercmodule make ${MAKEOPTS} -C ports/nrf BOARD=PCA10056 SD=s140 make ${MAKEOPTS} -C ports/nrf BOARD=PCA10090 } ######################################################################################## -# ports/powerpc +# ports/psoc-edge -function ci_powerpc_setup { - sudo apt-get update - sudo apt-get install gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross +function ci_psoc_edge_setup { + ci_gcc_arm_setup + sudo apt remove python3-packaging python3-jsonschema python3-cryptography + sudo pip3 install edgeprotecttools } -function ci_powerpc_build { - make ${MAKEOPTS} -C ports/powerpc UART=potato - make ${MAKEOPTS} -C ports/powerpc UART=lpc_serial +function ci_psoc_edge_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/psoc-edge submodules + make ${MAKEOPTS} -C ports/psoc-edge } ######################################################################################## @@ -385,6 +399,13 @@ function ci_qemu_setup_rv64 { qemu-system-riscv64 --version } +function ci_qemu_setup_ppc64 { + ci_gcc_ppc64_setup + sudo apt-get update + sudo apt-get install qemu-system + qemu-system-ppc64 --version +} + function ci_qemu_build_arm_prepare { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/qemu submodules @@ -402,7 +423,7 @@ function ci_qemu_build_arm_sabrelite { function ci_qemu_build_arm_thumb_softfp { ci_qemu_build_arm_prepare - make BOARD=MPS2_AN385 ${MAKEOPTS} -C ports/qemu test_full + make BOARD=MPS2_AN385 ${MAKEOPTS} -C ports/qemu USER_C_MODULES=../../examples/usercmodule test_full # Test building native .mpy with ARM-M softfp architectures. ci_native_mpy_modules_build armv6m @@ -446,6 +467,12 @@ function ci_qemu_build_rv64 { make ${MAKEOPTS} -C ports/qemu BOARD=VIRT_RV64 test_natmod } +function ci_qemu_build_ppc64 { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/qemu BOARD=POWERNV9 submodules + make ${MAKEOPTS} -C ports/qemu BOARD=POWERNV9 test +} + ######################################################################################## # ports/renesas-ra @@ -458,7 +485,7 @@ function ci_renesas_ra_board_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/renesas-ra submodules make ${MAKEOPTS} -C ports/renesas-ra BOARD=RA4M1_CLICKER - make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA6M2 + make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA6M2 USER_C_MODULES=../../examples/usercmodule make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA6M1 make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA4M1 make ${MAKEOPTS} -C ports/renesas-ra BOARD=EK_RA4W1 @@ -479,9 +506,11 @@ function ci_rp2_build { make ${MAKEOPTS} -C ports/rp2 submodules make ${MAKEOPTS} -C ports/rp2 make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO_W submodules + # Legacy USER_C_MODULES coverage on RPI_PICO_W. make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO_W USER_C_MODULES=../../examples/usercmodule/micropython.cmake make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO2 submodules - make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO2 + # Test c_module() on RPI_PICO2. + make ${MAKEOPTS} -C ports/rp2 BOARD=RPI_PICO2 FROZEN_MANIFEST="$(pwd)/tests/tools/manifest_c_module.py" make ${MAKEOPTS} -C ports/rp2 BOARD=W5100S_EVB_PICO submodules # This build doubles as a build test for disabling threads in the config make ${MAKEOPTS} -C ports/rp2 BOARD=W5100S_EVB_PICO CFLAGS_EXTRA=-DMICROPY_PY_THREAD=0 @@ -503,6 +532,7 @@ function ci_samd_build { make ${MAKEOPTS} -C ports/samd submodules make ${MAKEOPTS} -C ports/samd BOARD=ADAFRUIT_ITSYBITSY_M0_EXPRESS make ${MAKEOPTS} -C ports/samd BOARD=ADAFRUIT_ITSYBITSY_M4_EXPRESS + make ${MAKEOPTS} -C ports/samd BOARD=SPARKFUN_SAMD21_DEV_BREAKOUT } ######################################################################################## @@ -538,6 +568,12 @@ function ci_stm32_pyb_build { make ${MAKEOPTS} -C ports/stm32/mboot BOARD=STM32F769DISC CFLAGS_EXTRA='-DMBOOT_ADDRESS_SPACE_64BIT=1 -DMBOOT_SDCARD_ADDR=0x100000000ULL -DMBOOT_SDCARD_BYTE_SIZE=0x400000000ULL -DMBOOT_FSLOAD=1 -DMBOOT_VFS_FAT=1' } +function ci_stm32_build_cmod { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/stm32 submodules + make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 FROZEN_MANIFEST="$(pwd)/tests/tools/manifest_c_module.py" +} + function ci_stm32_nucleo_build { # This function builds the following MCU families: F0, H5, H7, L0, L4, WB. @@ -608,6 +644,18 @@ CI_UNIX_OPTS_QEMU_RISCV64=( MICROPY_STANDALONE=1 ) +CI_UNIX_OPTS_QEMU_LOONG64=( + CROSS_COMPILE=loongarch64-linux-gnu- + VARIANT=coverage + MICROPY_STANDALONE=1 +) + +CI_UNIX_OPTS_QEMU_X64=( + CROSS_COMPILE=x86_64-linux-gnu- + VARIANT=coverage + MICROPY_STANDALONE=1 +) + CI_UNIX_OPTS_SANITIZE_ADDRESS=( # Macro MP_ASAN allows detecting ASan on gcc<=13 CFLAGS_EXTRA="-fsanitize=address --param asan-use-after-return=0 -DMP_ASAN=1" @@ -623,9 +671,12 @@ CI_UNIX_OPTS_SANITIZE_UNDEFINED=( CI_UNIX_OPTS_REPR_B=( VARIANT=standard CFLAGS_EXTRA="-DMICROPY_OBJ_REPR=MICROPY_OBJ_REPR_B -DMICROPY_PY_UCTYPES=0 -Dmp_int_t=int32_t -Dmp_uint_t=uint32_t" - MICROPY_FORCE_32BIT=1 RUN_TESTS_MPY_CROSS_FLAGS="--mpy-cross-flags=\"-march=x86 -msmall-int-bits=30\"" +) +CI_UNIX_OPTS_X86=( + CROSS_COMPILE=i686-linux-gnu- + RUN_TESTS_MPY_CROSS_FLAGS=${RUN_TESTS_MPY_CROSS_FLAGS:-"--mpy-cross-flags=\"-march=x86\""} ) function ci_unix_build_helper { @@ -665,6 +716,13 @@ function ci_unix_run_tests_full_helper { ci_unix_run_tests_full_extra $micropython } +function ci_unix_run_native_mpy_tests_helper { + variant=$1 + shift + MICROPYPATH=examples/natmod/features2 ./ports/unix/build-$variant/micropython -m features2 + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-$variant/micropython ./run-natmodtests.py "$@" extmod/*.py) +} + function ci_native_mpy_modules_build { if [ "$1" = "" ]; then arch=x64 @@ -690,6 +748,14 @@ function ci_native_mpy_modules_32bit_build { ci_native_mpy_modules_build x86 } +function ci_native_mpy_modules_clang_build { + # This currently only supports the host architecture (assumed to be x64). + for natmod in btree deflate features1 features2 features3 features4 framebuf heapq random re + do + make -C examples/natmod/$natmod CC=clang + done +} + function ci_unix_minimal_build { make ${MAKEOPTS} -C ports/unix VARIANT=minimal } @@ -707,6 +773,10 @@ function ci_unix_standard_run_tests { ci_unix_run_tests_full_helper standard } +function ci_unix_standard_run_native_mpy_tests { + ci_unix_run_native_mpy_tests_helper standard "$@" +} + function ci_unix_standard_v2_build { ci_unix_build_helper VARIANT=standard MICROPY_PREVIEW_VERSION_2=1 ci_unix_build_ffi_lib_helper gcc @@ -716,6 +786,22 @@ function ci_unix_standard_v2_run_tests { ci_unix_run_tests_full_helper standard } +function ci_unix_standard_error_terse_build { + ci_unix_build_helper VARIANT=standard CFLAGS_EXTRA="-DMICROPY_ERROR_REPORTING=MICROPY_ERROR_REPORTING_TERSE" +} + +function ci_unix_standard_error_terse_run_tests { + make -C ports/unix VARIANT=standard test +} + +function ci_unix_standard_error_none_build { + ci_unix_build_helper VARIANT=standard CFLAGS_EXTRA="-DMICROPY_ERROR_REPORTING=MICROPY_ERROR_REPORTING_NONE" MICROPY_ROM_TEXT_COMPRESSION=0 +} + +function ci_unix_standard_error_none_run_tests { + make -C ports/unix VARIANT=standard test +} + function ci_unix_coverage_setup { pip3 install setuptools pip3 install pyelftools @@ -725,6 +811,7 @@ function ci_unix_coverage_setup { } function ci_unix_coverage_build { + # note: the coverage variant incorporates ../../examples/usercmodule, set in mpconfigvariant.mk ci_unix_build_helper VARIANT=coverage ci_unix_build_ffi_lib_helper gcc } @@ -741,6 +828,12 @@ function ci_unix_coverage_run_mpy_merge_tests { # Compile a selection of tests to .mpy and execute them, collecting the output. # None of the tests should SKIP. for inpy in $mptop/tests/basics/[acdel]*.py; do + if grep -q "import unittest" $inpy; then + # Merging >1 unittest-enabled module leads to unexpected + # results, as each file runs all previously registered unittest cases + echo "SKIPPING $inpy" + continue + fi test=$(basename $inpy .py) echo $test outmpy=$outdir/$test.mpy @@ -758,27 +851,26 @@ function ci_unix_coverage_run_mpy_merge_tests { } function ci_unix_coverage_run_native_mpy_tests { - MICROPYPATH=examples/natmod/features2 ./ports/unix/build-coverage/micropython -m features2 - (cd tests && ./run-natmodtests.py "$@" extmod/*.py) + ci_unix_run_native_mpy_tests_helper coverage "$@" } function ci_unix_32bit_setup { sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 + sudo apt-get install gcc-i686-linux-gnu g++-i686-linux-gnu patchelf libffi-dev:i386 python -m pip install pyelftools python -m pip install ar - gcc --version + i686-linux-gnu-gcc --version python3 --version } function ci_unix_coverage_32bit_build { - ci_unix_build_helper VARIANT=coverage MICROPY_FORCE_32BIT=1 - ci_unix_build_ffi_lib_helper gcc -m32 + ci_unix_build_helper VARIANT=coverage "${CI_UNIX_OPTS_X86[@]}" + ci_unix_build_ffi_lib_helper i686-linux-gnu-gcc } function ci_unix_coverage_32bit_run_tests { - ci_unix_run_tests_full_helper coverage MICROPY_FORCE_32BIT=1 + ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_X86[@]}" } function ci_unix_coverage_32bit_run_native_mpy_tests { @@ -786,8 +878,8 @@ function ci_unix_coverage_32bit_run_native_mpy_tests { } function ci_unix_nanbox_build { - ci_unix_build_helper VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1" - ci_unix_build_ffi_lib_helper gcc -m32 + ci_unix_build_helper VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1" "${CI_UNIX_OPTS_X86[@]}" + ci_unix_build_ffi_lib_helper i686-linux-gnu-gcc } function ci_unix_nanbox_run_tests { @@ -795,7 +887,8 @@ function ci_unix_nanbox_run_tests { } function ci_unix_longlong_build { - ci_unix_build_helper VARIANT=longlong "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" + ci_unix_build_helper VARIANT=longlong "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" "${CI_UNIX_OPTS_X86[@]}" + patchelf --add-rpath "/usr/i686-linux-gnu/lib" ports/unix/build-longlong/micropython } function ci_unix_longlong_run_tests { @@ -824,6 +917,7 @@ function ci_unix_gil_enabled_run_tests { function ci_unix_clang_setup { sudo apt-get update sudo apt-get install clang + pip3 install ar pyelftools clang --version } @@ -967,15 +1061,92 @@ function ci_unix_qemu_riscv64_run_tests { popd } +function ci_unix_qemu_loong64_setup { + sudo apt-get update + sudo apt-get install gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu libc6-loong64-cross libltdl-dev + sudo apt-get install qemu-user-static + qemu-loongarch64-static --version + sudo mkdir -p /usr/gnemul + sudo ln -s /usr/loongarch64-linux-gnu /usr/gnemul/qemu-loongarch64 + sudo ln -s /usr/bin/loongarch64-linux-gnu-gcc-14 /usr/bin/loongarch64-linux-gnu-gcc + sudo ln -s /usr/bin/loongarch64-linux-gnu-g++-14 /usr/bin/loongarch64-linux-gnu-g++ +} + +function ci_unix_qemu_loong64_build { + ci_unix_build_helper "${CI_UNIX_OPTS_QEMU_LOONG64[@]}" + ci_unix_build_ffi_lib_helper loongarch64-linux-gnu-gcc +} + +function ci_unix_qemu_loong64_run_tests { + # Issues with LOONG64 tests: + # - thread/stress_aes.py takes around 90 seconds + file ./ports/unix/build-coverage/micropython + # Loongarch64 isn't defined in the CI image's binfmt list. + cat << 'EOF' > ./ports/unix/build-coverage/micropython-runner +#!/bin/sh +MPY_PATH=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P) +qemu-loongarch64-static "$MPY_PATH"/micropython $@ +EOF + chmod +x ./ports/unix/build-coverage/micropython-runner + (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython-runner MICROPY_TEST_TIMEOUT=180 ./run-tests.py) +} + +function ci_unix_qemu_x64_setup { + sudo apt-get update + sudo apt-get install gcc-x86-64-linux-gnu g++-x86-64-linux-gnu libc6-amd64-cross libltdl-dev + sudo apt-get install qemu-user-static + python3 -m pip install pyelftools + python3 -m pip install ar + qemu-x86_64-static --version + sudo mkdir -p /usr/gnemul + sudo ln -s /usr/x86_64-linux-gnu /usr/gnemul/qemu-x86_64 +} + +function ci_unix_qemu_x64_build { + ci_unix_build_helper "${CI_UNIX_OPTS_QEMU_X64[@]}" + ci_unix_build_ffi_lib_helper x86_64-linux-gnu-gcc + ci_native_mpy_modules_build x64 +} + +function ci_unix_qemu_x64_run_tests { + # Issues with x64 tests on non-x64 hosts: + # - thread/stress_aes.py takes around 90 seconds + # - ports/unix/ffi_callback.py crashes QEMU (x86_64-binfmt-P: QEMU internal SIGSEGV {code=MAPERR, addr=0x20}) + file ./ports/unix/build-coverage/micropython + pushd tests + MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-tests.py --exclude '(thread/stress_aes.py|ports/unix/ffi_callback.py)' + MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-natmodtests.py extmod/btree*.py extmod/deflate*.py extmod/framebuf*.py extmod/heapq*.py extmod/random_basic*.py extmod/re*.py + popd +} + function ci_unix_repr_b_build { - ci_unix_build_helper "${CI_UNIX_OPTS_REPR_B[@]}" - ci_unix_build_ffi_lib_helper gcc -m32 + ci_unix_build_helper "${CI_UNIX_OPTS_REPR_B[@]}" "${CI_UNIX_OPTS_X86[@]}" + ci_unix_build_ffi_lib_helper i686-linux-gnu-gcc } function ci_unix_repr_b_run_tests { # ci_unix_run_tests_full_no_native_helper is not used due to # https://github.com/micropython/micropython/issues/18105 - ci_unix_run_tests_helper "${CI_UNIX_OPTS_REPR_B[@]}" + ci_unix_run_tests_helper "${CI_UNIX_OPTS_REPR_B[@]}" "${CI_UNIX_OPTS_X86[@]}" +} + +######################################################################################## +# ports/webassembly + +function ci_webassembly_setup { + npm install terser + git clone https://github.com/emscripten-core/emsdk.git + (cd emsdk && ./emsdk install latest && ./emsdk activate latest) +} + +function ci_webassembly_build { + source emsdk/emsdk_env.sh + make ${MAKEOPTS} -C ports/webassembly VARIANT=pyscript submodules + make ${MAKEOPTS} -C ports/webassembly VARIANT=pyscript +} + +function ci_webassembly_run_tests { + make -C ports/webassembly VARIANT=pyscript test_min } ######################################################################################## @@ -983,22 +1154,22 @@ function ci_unix_repr_b_run_tests { function ci_windows_setup { sudo apt-get update - sudo apt-get install gcc-mingw-w64 + sudo apt-get install gcc-mingw-w64 g++-mingw-w64 } function ci_windows_build { make ${MAKEOPTS} -C mpy-cross make ${MAKEOPTS} -C ports/windows submodules - make ${MAKEOPTS} -C ports/windows CROSS_COMPILE=i686-w64-mingw32- + make ${MAKEOPTS} -C ports/windows CROSS_COMPILE=i686-w64-mingw32- USER_C_MODULES=../../examples/usercmodule make ${MAKEOPTS} -C ports/windows CROSS_COMPILE=x86_64-w64-mingw32- BUILD=build-standard-w64 } ######################################################################################## # ports/zephyr -ZEPHYR_DOCKER_VERSION=v0.28.1 -ZEPHYR_SDK_VERSION=0.17.2 -ZEPHYR_VERSION=v4.2.0 +ZEPHYR_DOCKER_VERSION=v0.29.2 +ZEPHYR_SDK_VERSION=1.0.1 +ZEPHYR_VERSION=v4.4.0 function ci_zephyr_setup { IMAGE=ghcr.io/zephyrproject-rtos/ci:${ZEPHYR_DOCKER_VERSION} @@ -1038,9 +1209,9 @@ function ci_zephyr_install { function ci_zephyr_build { git submodule update --init lib/micropython-lib - docker exec zephyr-ci west build -p auto -b qemu_x86 -- -DCONF_FILE=prj_minimal.conf + docker exec zephyr-ci west build -p auto -b qemu_x86 -- -DCONF_FILE='prj_minimal.conf;boards/qemu_x86.conf' docker exec zephyr-ci west build -p auto -b frdm_k64f - docker exec zephyr-ci west build -p auto -b mimxrt1050_evk + docker exec zephyr-ci west build -p auto -b mimxrt1050_evk/mimxrt1052/qspi docker exec zephyr-ci west build -p auto -b nucleo_wb55rg # for bluetooth } @@ -1050,19 +1221,7 @@ function ci_zephyr_run_tests { } ######################################################################################## -# ports/alif - -function ci_alif_setup { - ci_gcc_arm_setup -} - -function ci_alif_ae3_build { - make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_HP submodules - make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_HE submodules - make ${MAKEOPTS} -C ports/alif BOARD=OPENMV_AE3 MCU_CORE=M55_DUAL - make ${MAKEOPTS} -C ports/alif BOARD=ALIF_ENSEMBLE MCU_CORE=M55_DUAL -} +# Helpers to run this script as a CLI tool. function _ci_help { # Note: these lines must be indented with tab characters (required by bash <<-EOF) diff --git a/tools/ci_check_duplicate_usb_vid_pid.py b/tools/ci_check_duplicate_usb_vid_pid.py index 66a9fb4c5a3..e9675bbbf06 100644 --- a/tools/ci_check_duplicate_usb_vid_pid.py +++ b/tools/ci_check_duplicate_usb_vid_pid.py @@ -119,10 +119,6 @@ def check_vid_pid(files, clusterlist): creation = CREATION_PATTERN.search(src_text) non_usb = usb_pattern.search(src_text) board_name = board_config.parts[-2] - port_name = board_config.parts[-4] - - if port_name == "renode": - continue if usb_vid and usb_pid: id_group = f"0x{int(usb_vid.group(1), 16):04X}:0x{int(usb_pid.group(1), 16):04X}" diff --git a/tools/ci_download_sizes.mjs b/tools/ci_download_sizes.mjs new file mode 100755 index 00000000000..4e199ab368c --- /dev/null +++ b/tools/ci_download_sizes.mjs @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) +// +// SPDX-License-Identifier: MIT + +// Download every board's firmware size record from this run's artifacts, by exact name. +// +// build_release_files.py records a board's sizes in sizes/.json and the board job +// uploads it as the artifact zz-sizes-. actions/download-artifact matches a name +// pattern against a listing of the run's artifacts, and that listing stops at 1000, which +// a full build exceeds. Looking each record up by name has no such limit and uses the same +// internal artifact API, so it costs nothing against the REST rate limit. +// +// That API needs the runner's ACTIONS_RUNTIME_TOKEN, which only `uses:` action steps get, +// so build.yml calls downloadSizes() from an actions/github-script step rather than from +// `node` in a `run:` step. @actions/artifact must be installed where this file can resolve +// it: `npm install` at the repository root. + +import { DefaultArtifactClient } from "@actions/artifact"; + +// The boards in the scheduler job's "ports" output: {: [boards...], ports: [...]}. +export function boardsFromSchedule(schedule) { + return (schedule.ports ?? []).flatMap((port) => schedule[port]); +} + +export async function downloadSizes({ boards, outDir, info = console.log, warning = console.warn }) { + const client = new DefaultArtifactClient(); + const missing = []; + let downloaded = 0; + + async function fetchRecord(board) { + try { + const { artifact } = await client.getArtifact(`zz-sizes-${board}`); + await client.downloadArtifact(artifact.id, { path: outDir }); + downloaded++; + } catch (error) { + missing.push(board); + info(`No size record for ${board}: ${error.message}`); + } + } + + // A few downloads at a time: enough to get through a full build in a minute or two, + // few enough not to trip the artifact service's throttling. + const queue = [...boards]; + await Promise.all( + Array.from({ length: 8 }, async () => { + while (queue.length) { + await fetchRecord(queue.shift()); + } + }), + ); + + info(`Downloaded ${downloaded} of ${boards.length} size records to ${outDir}`); + if (missing.length) { + warning(`No firmware size record for ${missing.length} board(s): ${missing.join(" ")}`); + } + return { downloaded, missing }; +} diff --git a/tools/ci_fetch_deps.py b/tools/ci_fetch_deps.py index f101c99e469..8ef4df9fc6f 100644 --- a/tools/ci_fetch_deps.py +++ b/tools/ci_fetch_deps.py @@ -54,7 +54,6 @@ def _all_submodules(): "lib/tlsf", "data/nvm.toml/", ], - "broadcom": ["extmod/ulab/", "lib/tlsf", "lib/tinyusb/"], "cxd56": ["extmod/ulab/", "lib/tlsf", "lib/tinyusb/"], "espressif": [ "extmod/ulab/", @@ -65,7 +64,6 @@ def _all_submodules(): "lib/tlsf", "lib/tinyusb/", ], - "litex": ["extmod/ulab/", "lib/tinyusb/", "lib/tlsf"], "mimxrt10xx": ["extmod/ulab/", "lib/mp3/", "lib/tinyusb/", "lib/tlsf", "data/nvm.toml/"], "nordic": [ "extmod/ulab/", @@ -88,7 +86,6 @@ def _all_submodules(): "lib/tlsf", "data/nvm.toml/", ], - "renode": ["lib/tlsf"], "silabs": ["extmod/ulab/", "data/nvm.toml/", "lib/tlsf"], "stm": [ "extmod/ulab/", diff --git a/tools/ci_firmware_sizes.py b/tools/ci_firmware_sizes.py new file mode 100755 index 00000000000..ad8be014c21 --- /dev/null +++ b/tools/ci_firmware_sizes.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) +# +# SPDX-License-Identifier: MIT + +"""Merge the per-board firmware size records of a CI run into one report. + +build_release_files.py writes sizes/.json for every board it builds, and the board +job uploads it as the artifact zz-sizes-. The build-ci job downloads those into one +directory and runs: + + ci_firmware_sizes.py + +which writes + + /0-sizes.json every board with its per-language builds and derived free flash + /0-sizes.html a sortable, filterable table of the same boards, data embedded + +The names match the artifacts build-ci uploads them as, one file each, unzipped. The +CircuitPython version, from --version or $CP_VERSION, goes into the reports. The script +also appends a line to $GITHUB_STEP_SUMMARY when that is set, or to --summary FILE, +saying how many boards were built and where the reports are. The run description in the +reports comes from the GITHUB_* environment variables; outside CI the report says so. +""" + +import argparse +import collections +import datetime +import html +import json +import os +import pathlib +import sys + +# Both reports share this name; build-ci uploads each as an artifact of the same name. +REPORT_NAME = "0-sizes" + + +def load_records(records_dir): + """Read every sizes/.json under records_dir, keyed by board.""" + records = {} + for path in sorted(pathlib.Path(records_dir).rglob("*.json")): + with path.open("r") as f: + record = json.load(f) + if not isinstance(record, dict) or "languages" not in record: + print(f"Skipping {path}: not a size record", file=sys.stderr) + continue + records[record["board"]] = record + return records + + +def summarize(record): + """Add the derived fields the table and the summary sort and colour by.""" + region = record.get("region") + languages = record["languages"] + board = { + "port": record["port"], + "board": record["board"], + "region": region, + "en_US": (languages.get("en_US") or {}).get("used"), + "largest_language": None, + "largest_used": None, + "largest_source": None, + "min_free": None, + "pct": None, + "measured": sum(1 for b in languages.values() if b["source"] == "measured"), + "predicted": sum(1 for b in languages.values() if b["source"] == "predicted"), + "failed": sorted(lang for lang, b in languages.items() if b["status"] == "failed"), + "languages": languages, + } + sized = [(b["used"], lang) for lang, b in languages.items() if b["used"] is not None] + if sized: + used, lang = max(sized) + board["largest_language"] = lang + board["largest_used"] = used + board["largest_source"] = languages[lang]["source"] + if region: + board["min_free"] = region - used + board["pct"] = round(100.0 * used / region, 2) + return board + + +def run_info(version): + """Where the numbers came from, from the variables GitHub Actions sets.""" + env = os.environ + info = { + "version": version, + "repository": env.get("GITHUB_REPOSITORY"), + "run_id": env.get("GITHUB_RUN_ID"), + "event": env.get("GITHUB_EVENT_NAME"), + "ref": env.get("GITHUB_REF_NAME"), + "sha": env.get("GITHUB_SHA"), + "url": None, + } + if info["repository"] and info["run_id"]: + server = env.get("GITHUB_SERVER_URL", "https://github.com") + info["url"] = f"{server}/{info['repository']}/actions/runs/{info['run_id']}" + return info + + +def describe_run(info): + """One sentence for the report header.""" + if not info["url"]: + return "Built outside GitHub Actions." + parts = [f"{info['event']} build" if info["event"] else "build"] + if info["version"]: + parts.append(f"of CircuitPython {info['version']}") + elif info["ref"]: + parts.append(f"of {info['ref']}") + if info["sha"]: + parts.append(f"at {info['sha'][:10]}") + return " ".join(parts) + + +def sort_key(board): + # Fullest first; boards without a measurable region go last, alphabetically. + return (board["pct"] is None, -(board["pct"] or 0), board["board"]) + + +def write_json(boards, info, out_dir): + document = { + "generated": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds"), + "run": info, + "boards": boards, + } + with (out_dir / f"{REPORT_NAME}.json").open("w") as f: + json.dump(document, f, indent=1) + f.write("\n") + + +HTML_TEMPLATE = r""" + + + + +Firmware Sizes + + + +

Firmware sizes

+

__RUN__ __COUNTS__ +"Largest" is the language build that uses the most flash, which is what has to fit. +A size in grey was predicted for a language that was not built. +Click a column header to sort.

+
+ + +
+
Free flash: + + + + + +
+
+
+ + + + + + + + + + + + +
PortBoardRegionen_US usedLargest langLargest usedMin free% fullLangs
+ + + +""" + + +def write_html(boards, info, out_dir): + if info["url"]: + run = 'From GitHub Actions run {run_id} ({desc}).'.format( + url=html.escape(info["url"], quote=True), + run_id=html.escape(info["run_id"]), + desc=html.escape(describe_run(info)), + ) + else: + run = html.escape(describe_run(info)) + with_region = sum(1 for b in boards if b["region"]) + counts = f"{len(boards)} boards, {with_region} with a measurable flash region." + # The table rows carry only what the page shows; the per-language detail is in + # the JSON report. "6}" + "".join(f" {label:>8}" for label in labels)) + for name in sorted(ports): + c = ports[name] + print(f"{name:16} {c['boards']:6}" + "".join(f" {c[label]:8}" for label in labels)) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) + parser.add_argument("records_dir", help="directory holding the per-board size records") + parser.add_argument("out_dir", help="where to write the JSON and HTML reports") + parser.add_argument( + "--version", + default=os.environ.get("CP_VERSION"), + help="CircuitPython version named in the reports (default: $CP_VERSION)", + ) + parser.add_argument( + "--summary", + default=os.environ.get("GITHUB_STEP_SUMMARY"), + help="append the markdown summary to this file (default: $GITHUB_STEP_SUMMARY)", + ) + args = parser.parse_args() + + records = load_records(args.records_dir) + boards = sorted((summarize(r) for r in records.values()), key=sort_key) + info = run_info(args.version) + + out_dir = pathlib.Path(args.out_dir) + out_dir.mkdir(parents=True, exist_ok=True) + write_json(boards, info, out_dir) + write_html(boards, info, out_dir) + + summary = markdown_summary(boards) + if args.summary: + with open(args.summary, "a") as f: + f.write(summary) + else: + print(summary) + print_ports(boards) + + +if __name__ == "__main__": + main() diff --git a/tools/ci_set_matrix.py b/tools/ci_set_matrix.py index 26d3bed4ebc..f95bc1cfb10 100755 --- a/tools/ci_set_matrix.py +++ b/tools/ci_set_matrix.py @@ -28,6 +28,7 @@ import json import pathlib import subprocess +import tomllib from concurrent.futures import ThreadPoolExecutor tools_dir = pathlib.Path(__file__).resolve().parent @@ -54,6 +55,7 @@ ".github/workflows/run-tests.yml", ".github/workflows/run-zephyr-tests.yml", ".github/workflows/build-board-custom.yml", + ".github/workflows/zephyr-autogen-pr.yml", ".github/workflows/bundle_cron.yml", ".github/workflows/create-website-pr.yml", ".github/workflows/learn_cron.yml", @@ -73,8 +75,40 @@ # The Zephyr tests build native_sim and the two bsim boards out of the shared sources, so a # change confined to these cannot reach them: another port, a translation, a frozen library -# (this port has none), documentation or the unix test suite. -PATTERN_ZEPHYR_TESTS_IGNORE = re.compile(r"^(?:docs|frozen|locale|tests)/|^ports/(?!zephyr-cp/)") +# (this port has none), documentation, the unix test suite or the autogen PR workflow. +PATTERN_ZEPHYR_TESTS_IGNORE = re.compile( + r"^(?:docs|frozen|locale|tests)/|^ports/(?!zephyr-cp/)" + r"|^\.github/workflows/zephyr-autogen-pr\.yml$" +) + +# Zephyr boards don't use make, so their module tables can't be computed here. Each +# board's build writes autogen_board_info.toml next to its circuitpython.toml and that +# file is committed; a board whose table is missing, unreadable or doesn't name a +# module is built. +ZEPHYR_BOARDS = tools_dir.parent / "ports" / "zephyr-cp" / "boards" +zephyr_modules = None + + +def load_zephyr_modules(): + modules = {} + for board_info in ZEPHYR_BOARDS.glob("*/*/autogen_board_info.toml"): + board = f"{board_info.parent.parent.name}_{board_info.parent.name}" + try: + with board_info.open("rb") as f: + modules[board] = tomllib.load(f)["modules"] + except Exception as e: # noqa: BLE001 -- whatever went wrong, the board gets built + print(f" {board}: unusable module table ({e})") + return modules + + +def zephyr_board_has_module(board, module): + """What the board's committed module table says. An unknown board or module counts + as yes, so the board gets built.""" + global zephyr_modules + if zephyr_modules is None: + zephyr_modules = load_zephyr_modules() + return board not in zephyr_modules or zephyr_modules[board].get(module, True) + PATTERN_WINDOWS = { ".github/", @@ -198,7 +232,9 @@ def get_settings(board): module_matches = pattern_module.search(file) port = port_matches.group(1) if port_matches else None if port and not module_matches: - if port != "unix": + # The port may have been removed entirely. In that case, its + # boards are gone too, so there is nothing to build for it. + if port != "unix" and port in port_to_board: boards_to_build.update(port_to_board[port]) continue @@ -208,11 +244,18 @@ def get_settings(board): # Take a copy, because we remove items from it below. For # instance, if we remove items from, say, all_board_ids, then # the logic to build all boards breaks. - boards = set(port_to_board[port] if port else all_board_ids) - - # Zephyr boards don't use make, so build them and don't compute their settings. - for board in port_to_board["zephyr-cp"]: - if board in boards: + boards = set(port_to_board.get(port, ()) if port else all_board_ids) + + # Zephyr boards don't use make, so decide them here from their committed + # module table and leave them out of the settings computation below. + module = module_matches.group(2) if module_matches else None + for board in list(boards): # a copy, boards shrinks below + if board_to_port[board] != "zephyr-cp": + continue + boards.remove(board) + if file.startswith("frozen"): + continue # the port has no frozen modules + if module is None or zephyr_board_has_module(board, module): boards_to_build.add(board) for board in boards_to_build: diff --git a/tools/gen_display_resources.py b/tools/gen_display_resources.py index 9ed829f7d37..f857754e95f 100644 --- a/tools/gen_display_resources.py +++ b/tools/gen_display_resources.py @@ -76,7 +76,7 @@ def _load_row(self, y, row): print("Font missing", missing, "characters", file=sys.stderr) tile_x, tile_y, dx, dy = f.get_bounding_box() -total_bits = tile_x * len(all_characters) +total_bits = tile_x * len(filtered_characters) total_bits += 32 - total_bits % 32 bytes_per_row = total_bits // 8 b = bytearray(bytes_per_row * tile_y) @@ -283,7 +283,7 @@ def _load_row(self, y, row): .inline_tiles = false, .in_group = true }}; -""".format(len(all_characters), tile_x, tile_y) +""".format(len(filtered_characters), tile_x, tile_y) ) c_file.write( @@ -311,7 +311,7 @@ def _load_row(self, y, row): .inline_tiles = false, .in_group = true }}; -""".format(len(all_characters), tile_x, tile_y) +""".format(len(filtered_characters), tile_x, tile_y) ) c_file.write( @@ -345,7 +345,7 @@ def _load_row(self, y, row): .bitmask = 0x01, .read_only = true }}; -""".format(len(all_characters) * tile_x, tile_y, bytes_per_row / 4) +""".format(len(filtered_characters) * tile_x, tile_y, bytes_per_row / 4) ) diff --git a/tools/makemanifest.py b/tools/makemanifest.py index 860935397af..97a708a68a3 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -136,6 +136,9 @@ def main(): ) cmd_parser.add_argument("-v", "--var", action="append", help="variables to substitute") cmd_parser.add_argument("--mpy-tool-flags", default="", help="flags to pass to mpy-tool") + cmd_parser.add_argument( + "--list-c-modules", action="store_true", help="list C module paths from manifest and exit" + ) cmd_parser.add_argument("files", nargs="+", help="input manifest list") args = cmd_parser.parse_args() @@ -150,6 +153,32 @@ def main(): print("MPY_DIR and PORT_DIR variables must be specified") sys.exit(1) + # Use a lighter mode when only listing C modules: freeze/package/module + # become no-ops so the listing doesn't fail on freeze targets that may not + # exist for every variant of the board (e.g. alif's modules/$(MCU_CORE)). + mode = manifestfile.MODE_LIST_C_MODULES if args.list_c_modules else manifestfile.MODE_FREEZE + manifest = manifestfile.ManifestFile(mode, VARS) + + # Include top-level inputs, to generate the manifest + for input_manifest in args.files: + try: + manifest.execute(input_manifest) + except manifestfile.ManifestFileError as er: + print( + 'manifest error executing "{}": {}'.format(input_manifest, er.args[0]), + file=sys.stderr, + ) + sys.exit(1) + + # If we're just listing C modules, output them and exit + if args.list_c_modules: + c_modules = manifest.c_modules() + if c_modules: + # Output one path per line to handle paths with spaces + for module in c_modules: + print(module) + sys.exit(0) + # Get paths to tools MPY_CROSS = VARS["MPY_DIR"] + "/mpy-cross/build/mpy-cross" if sys.platform == "win32": @@ -162,16 +191,6 @@ def main(): print("mpy-cross not found at {}, please build it first".format(MPY_CROSS)) sys.exit(1) - manifest = manifestfile.ManifestFile(manifestfile.MODE_FREEZE, VARS) - - # Include top-level inputs, to generate the manifest - for input_manifest in args.files: - try: - manifest.execute(input_manifest) - except manifestfile.ManifestFileError as er: - print('freeze error executing "{}": {}'.format(input_manifest, er.args[0])) - sys.exit(1) - # Process the manifest str_paths = [] mpy_files = [] @@ -244,10 +263,11 @@ def main(): b'#include "py/emitglue.h"\n' b"extern const qstr_pool_t mp_qstr_const_pool;\n" b"const qstr_pool_t mp_qstr_frozen_const_pool = {\n" + # CIRCUITPY-CHANGE: blob and offsets fields b" #if MICROPY_QSTR_BYTES_IN_HASH\n" - b" (qstr_pool_t*)&mp_qstr_const_pool, MP_QSTRnumber_of, 0, 0, 0, NULL, NULL, {},\n" + b" (qstr_pool_t*)&mp_qstr_const_pool, MP_QSTRnumber_of, 0, 0, 0, NULL, NULL, NULL, NULL, {},\n" b" #else\n" - b" (qstr_pool_t*)&mp_qstr_const_pool, MP_QSTRnumber_of, 0, 0, 0, NULL, {},\n" + b" (qstr_pool_t*)&mp_qstr_const_pool, MP_QSTRnumber_of, 0, 0, 0, NULL, NULL, NULL, {},\n" b" #endif\n" b"};\n" b'const char mp_frozen_names[] = { MP_FROZEN_STR_NAMES "\\0"};\n' diff --git a/tools/manifestfile.py b/tools/manifestfile.py index 9c7a6e140f9..2e2ade6783c 100644 --- a/tools/manifestfile.py +++ b/tools/manifestfile.py @@ -27,6 +27,7 @@ import contextlib import os +import re import sys import glob import tempfile @@ -40,6 +41,9 @@ MODE_COMPILE = 2 # Same as compile, but handles require(..., pypi="name") as a requirements.txt entry. MODE_PYPROJECT = 3 +# Same surface as MODE_FREEZE, but freeze/package/module are no-ops so callers +# can collect c_module() entries without touching files referenced by freeze(). +MODE_LIST_C_MODULES = 4 # In compile mode, .py -> KIND_COMPILE_AS_MPY # In freeze mode, .py -> KIND_FREEZE_AS_MPY, .mpy->KIND_FREEZE_MPY @@ -192,6 +196,8 @@ def __init__(self, mode, path_vars=None): self._manifest_files = [] # List of PyPI dependencies (when mode=MODE_PYPROJECT). self._pypi_dependencies = [] + # List of C module directories. + self._c_modules = [] # Don't allow including the same file twice. self._visited = set() # Stack of metadata for each level. @@ -201,7 +207,9 @@ def __init__(self, mode, path_vars=None): # List of directories to search for packages. self._library_dirs = [] # Add default micropython-lib libraries if $(MPY_LIB_DIR) has been specified. - if self._path_vars["MPY_LIB_DIR"]: + # Use .get() to avoid KeyError if MPY_LIB_DIR wasn't passed (shouldn't happen + # in normal cmake/make builds, but be defensive for direct tool invocations). + if self._path_vars.get("MPY_LIB_DIR"): for lib in BASE_LIBRARY_NAMES: self.add_library(lib, os.path.join("$(MPY_LIB_DIR)", lib)) @@ -221,11 +229,12 @@ def _manifest_globals(self, kwargs): "add_library": self.add_library, "package": self.package, "module": self.module, + "c_module": self.c_module, "options": IncludeOptions(**kwargs), } # Extra legacy functions only for freeze mode. - if self._mode == MODE_FREEZE: + if self._mode in (MODE_FREEZE, MODE_LIST_C_MODULES): g.update( { "freeze": self.freeze, @@ -244,6 +253,9 @@ def pypi_dependencies(self): # In MODE_PYPROJECT, this will return a list suitable for requirements.txt. return self._pypi_dependencies + def c_modules(self): + return self._c_modules + def execute(self, manifest_file): if manifest_file.endswith(".py"): # Execute file from filesystem. @@ -478,6 +490,9 @@ def package(self, package_path, files=None, base_path=".", opt=None): """ self._metadata[-1].check_initialised(self._mode) + if self._mode == MODE_LIST_C_MODULES: + return + # Include "base_path/package_path/**/*.py" --> "package_path/**/*.py" self._search(base_path, package_path, files, exts=(".py",), kind=KIND_AUTO, opt=opt) @@ -493,6 +508,9 @@ def module(self, module_path, base_path=".", opt=None): """ self._metadata[-1].check_initialised(self._mode) + if self._mode == MODE_LIST_C_MODULES: + return + # Include "base_path/module_path" --> "module_path" base_path = self._resolve_path(base_path) _, ext = os.path.splitext(module_path) @@ -501,7 +519,56 @@ def module(self, module_path, base_path=".", opt=None): # TODO: version None self._add_file(os.path.join(base_path, module_path), module_path, opt=opt) + def c_module(self, module_path): + """ + Include a C module directory in the build. + + The module_path should be a directory containing a micropython.mk and/or + micropython.cmake file. + + Supports $(VAR) path substitution: + c_module("$(MPY_DIR)/examples/usercmodule/cexample") + c_module("$(BOARD_DIR)/../../drivers/sensor") + + Can be called multiple times to include multiple C modules. + + Only has effect when collecting modules for the build system + (MODE_FREEZE / MODE_LIST_C_MODULES). Silent no-op in MODE_COMPILE / + MODE_PYPROJECT, so the same manifest can be evaluated in any mode. + """ + if self._mode not in (MODE_FREEZE, MODE_LIST_C_MODULES): + return + resolved = self._resolve_path(module_path) + # Reject unresolved $(VAR) up front so a bad manifest fails with a + # clear message rather than a confusing "path does not exist" containing + # the variable literal. + unresolved = re.search(r"\$\([^)]+\)", resolved) + if unresolved: + raise ManifestFileError( + "Unresolved variable in c_module() path: {}".format(unresolved.group(0)) + ) + module_path = resolved + if not os.path.exists(module_path): + raise ManifestFileError("C module path does not exist: {}".format(module_path)) + if not os.path.isdir(module_path): + raise ManifestFileError("C module path must be a directory: {}".format(module_path)) + # Verify the directory contains a micropython.mk or micropython.cmake file. + has_mk = os.path.isfile(os.path.join(module_path, "micropython.mk")) + has_cmake = os.path.isfile(os.path.join(module_path, "micropython.cmake")) + if not has_mk and not has_cmake: + raise ManifestFileError( + "C module directory must contain micropython.mk or micropython.cmake: {}".format( + module_path + ) + ) + self._c_modules.append(module_path) + def _freeze_internal(self, path, script, exts, kind, opt): + # In list-c-modules mode we only care about c_module() entries; skip + # the file-system walk so freeze targets that don't exist (e.g. a + # board-conditional modules directory) don't abort the listing. + if self._mode == MODE_LIST_C_MODULES: + return if script is None: self._search(path, None, None, exts=exts, kind=kind, opt=opt) elif isinstance(script, str) and os.path.isdir(os.path.join(path, script)): @@ -555,6 +622,8 @@ def freeze_as_str(self, path): Freeze the given `path` and all .py scripts within it as a string, which will be compiled upon import. """ + if self._mode == MODE_LIST_C_MODULES: + return self._search(path, None, None, exts=(".py",), kind=KIND_FREEZE_AS_STR) def freeze_as_mpy(self, path, script=None, opt=None): diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index bf0b89018e4..8541d1a0118 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -1511,6 +1511,8 @@ def read_raw_code(reader, parent_name, qstr_table, obj_table, segments): def read_mpy(filename): + import re + with open(filename, "rb") as fileobj: reader = MPYReader(filename, fileobj) segments = [] @@ -1556,7 +1558,10 @@ def read_mpy(filename): obj_table.append(read_obj(reader, segments)) # Compute the compiled-module escaped name. - cm_escaped_name = qstr_table[0].str.replace("/", "_")[:-3] + module_name = qstr_table[0].str + if not all(0x7F >= ord(codepoint) >= 0x20 for codepoint in module_name): + raise MPYReadError(filename, "cannot have unicode characters in module name") + cm_escaped_name = re.sub("[^a-zA-Z0-9_]", "_", "_".join(module_name.split(".")[:-1])) # Read the outer raw code, which will in turn read all its children. raw_code_file_offset = reader.tell() @@ -1673,12 +1678,27 @@ def freeze_mpy(firmware_qstr_idents, compiled_modules): print(" %d," % qhash) qstr_content += config.MICROPY_QSTR_BYTES_IN_HASH print("};") + # CIRCUITPY-CHANGE: ROM pools store their strings in one NUL-separated blob + # addressed by 16-bit offsets, with a trailing end-of-blob offset. See py/qstr.h. print() - print("const qstr_len_t mp_qstr_frozen_const_lengths[] = {") - for _, _, _, qbytes in new: - print(" %d," % len(qbytes)) - qstr_content += config.MICROPY_QSTR_BYTES_IN_LEN - qstr_content += len(qbytes) + 1 # include NUL + print("const char mp_qstr_frozen_const_blob[] =") + offsets = [] + offset = 0 + for _, _, qstr, qbytes in new: + print(' "%s" "\\0"' % qstrutil.escape_bytes(qstr, qbytes)) + offsets.append(offset) + offset += len(qbytes) + 1 # include NUL + qstr_content += len(qbytes) + 1 + print(" ;") + if offset >= qstrutil.QSTR_BLOB_MAX: + raise SystemExit("frozen qstr blob is too large: %d bytes" % offset) + print() + print("const qstr_offset_t mp_qstr_frozen_const_offsets[] = {") + for offset in offsets: + print(" %d," % offset) + qstr_content += 2 + print(" sizeof(mp_qstr_frozen_const_blob) - 1,") + qstr_content += 2 print("};") print() print("extern const qstr_pool_t mp_qstr_const_pool;") @@ -1690,11 +1710,9 @@ def freeze_mpy(firmware_qstr_idents, compiled_modules): print(" %u, // used entries" % len(new)) if config.MICROPY_QSTR_BYTES_IN_HASH: print(" (qstr_hash_t *)mp_qstr_frozen_const_hashes,") - print(" (qstr_len_t *)mp_qstr_frozen_const_lengths,") - print(" {") - for _, _, qstr, qbytes in new: - print(' "%s",' % qstrutil.escape_bytes(qstr, qbytes)) - print(" },") + print(" NULL, // lengths; strings live in the blob") + print(" mp_qstr_frozen_const_blob,") + print(" mp_qstr_frozen_const_offsets,") print("};") # Freeze all modules. @@ -1737,7 +1755,7 @@ def freeze_mpy(firmware_qstr_idents, compiled_modules): if module_name.endswith("/__init__.py"): short_name = module_name[: -len("/__init__.py")] else: - short_name = module_name[: -len(".py")] + short_name = ".".join(module_name.split(".")[:-1]) print('MICROPY_FROZEN_LIST_ITEM("%s", "%s")' % (short_name, module_name)) print("#endif") diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index 56734b3d170..abb9ab4f4bd 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -29,7 +29,7 @@ """ import sys, os, struct, re -from elftools.elf import elffile +from elftools.elf import elffile, relocation import ar_util sys.path.append(os.path.dirname(__file__) + "/../py") @@ -123,12 +123,14 @@ R_RISCV_32_PCREL = 57 R_RISCV_PLT32 = 59 R_XTENSA_PDIFF32 = 59 +R_XTENSA_NDIFF32 = 62 R_RISCV_SET_ULEB128 = 60 R_RISCV_SUB_ULEB128 = 61 R_RISCV_TLSDESC_HI20 = 62 R_RISCV_TLSDESC_LOAD_LO12 = 63 R_RISCV_TLSDESC_ADD_LO12 = 64 R_RISCV_TLSDESC_CALL = 65 +R_ARM_GOT_PREL = 96 ################################################################################ # Architecture configuration @@ -143,10 +145,16 @@ def fit_signed(bits, value): def asm_jump_x86(entry): - return struct.pack("> 1) & 0x07FF) @@ -156,7 +164,7 @@ def asm_jump_thumb(entry): # push {r0, lr} # bl # pop {r0, pc} - entry += 2 # skip "push {r0, lr}" + entry -= 2 # skip "push {r0, lr}" b0 = 0xB400 | 0x0100 | 0x0001 # push, lr, r0 b1 = 0xF000 | ((entry >> 12) & 0x07FF) b2 = 0xF800 | ((entry >> 1) & 0x07FF) @@ -165,6 +173,7 @@ def asm_jump_thumb(entry): def asm_jump_thumb2(entry): + entry -= 4 if fit_signed(11, entry): # Signed value fits in 12 bits b0 = 0xE000 | ((entry >> 1) & 0x07FF) @@ -177,36 +186,96 @@ def asm_jump_thumb2(entry): def asm_jump_xtensa(entry): - if fit_signed(17, entry): - jump_op = (entry - 4) << 6 | 6 + if fit_signed(17, entry - 8): + jump_op = ((entry - 8) << 6) | 6 return struct.pack("> 8) else: - raise LinkError("Large jumps are not yet supported on Xtensa") + raise LinkError("jumps larger than 128KiB are not supported") def asm_jump_riscv(entry): if fit_signed(11, entry): - entry += 2 # c.j entry return struct.pack( "> 2) + | ((entry & 0x300) << 1) | ((entry & 0x80) >> 1) | ((entry & 0x40) << 1) | ((entry & 0x20) >> 3) - | ((entry & 0x10) << 7), + | ((entry & 0x10) << 7) + | ((entry & 0x0E) << 2), ) - else: + elif fit_signed(31, entry - 8): # auipc t6, HI(entry) # jalr zero, t6, LO(entry) - upper, lower = split_riscv_address(entry + 8) + upper, lower = split_riscv_address(entry) return struct.pack( "") + env.sections.insert(1, section) + r = relocation.Relocation({}, None) + r.index = get_memx_function_index(n) + r.offset = reloc + section.reloc.append(r) + section.reloc_name = "unknown" + sym.section = section else: undef_errors.append("{}: undefined symbol: {}".format(sym.filename, sym.name)) @@ -1285,8 +1426,24 @@ def link_objects(env, native_qstr_vals_len): raise LinkError("\n".join(undef_errors)) # Generate the entry trampoline assuming the offset is already known. - env.entry_point = env.find_entry_addr() - jump = env.arch.asm_jump(env.entry_point) + + text_alignment = env.find_sym("mpy_init").section.alignment + if env.arch.name in ("EM_386", "EM_X86_64"): + show_warning = text_alignment not in (1, 4) + elif env.arch.name in ("EM_ARM", "EM_XTENSA"): + show_warning = text_alignment != 4 + elif env.arch.name == "EM_RISCV": + show_warning = text_alignment != 2 + else: + show_warning = True + + if show_warning: + log( + LOG_LEVEL_1, + f"A .text section with an alignment of {text_alignment} bytes for {env.arch.name} is not tested and may not work", + ) + + jump = generate_entry_point_jump(env) env.entry_trampoline_len = len(jump) # Align sections, assign their addresses, and create full_text @@ -1323,6 +1480,8 @@ def link_objects(env, native_qstr_vals_len): do_relocation_text(env, sec.addr, r) elif sec.name.startswith(".data.rel.ro"): do_relocation_data(env, sec.addr, r) + elif sec.name.startswith(".internal"): + env.mpy_relocs.append((".text", sec.addr + r.offset, r.index)) else: assert 0, sec.name @@ -1330,6 +1489,15 @@ def link_objects(env, native_qstr_vals_len): ################################################################################ # .mpy output +MP_FUN_TABLE_MEMSET = 50 +MP_FUN_TABLE_MEMMOVE = 51 + + +def get_memx_function_index(f): + if f == "memset": + return MP_FUN_TABLE_MEMSET + return MP_FUN_TABLE_MEMMOVE + class MPYOutput: def open(self, fname): @@ -1362,6 +1530,12 @@ def write_qstr(self, s): self.write_bytes(b"\x00") def write_reloc(self, base, offset, dest, n): + if dest > 2 and n > 1: + # dest>2 cannot encode n, so do it manually. + for _ in range(n): + self.write_reloc(base, offset, dest, 1) + offset += 1 + return need_offset = not (base == self.prev_base and offset == self.prev_offset + 1) self.prev_offset = offset + n - 1 if dest <= 2: @@ -1382,11 +1556,11 @@ def write_reloc(self, base, offset, dest, n): self.write_uint(n) -def build_mpy(env, fmpy, native_qstr_vals, arch_flags): +def build_mpy(env, fmpy, internal_name, native_qstr_vals, arch_flags): # Rewrite the entry trampoline if the proper value isn't known earlier, and # ensure the trampoline size remains the same. if env.arch.delayed_entry_offset: - jump = env.arch.asm_jump(env.find_entry_addr()) + jump = generate_entry_point_jump(env) env.full_text[: len(jump)] = jump assert len(jump) == env.entry_trampoline_len @@ -1421,7 +1595,7 @@ def build_mpy(env, fmpy, native_qstr_vals, arch_flags): out.write_uint(0) # MPY: qstr table - out.write_qstr(fmpy) # filename + out.write_qstr(internal_name) # filename for q in native_qstr_vals: out.write_qstr(q) @@ -1507,19 +1681,13 @@ def do_preprocess(args): args.output = args.files[0][:-1] + "config.h" static_qstrs, qstr_vals = extract_qstrs(args.files) with open(args.output, "w") as f: - print( - "#include \n" - "typedef uintptr_t mp_uint_t;\n" - "typedef intptr_t mp_int_t;\n" - "typedef uintptr_t mp_off_t;", - file=f, - ) + print("#include \ntypedef uintptr_t mp_off_t;", file=f) for i, q in enumerate(static_qstrs): print("#define %s (%u)" % (q, i + 1), file=f) for i, q in enumerate(sorted(qstr_vals)): print("#define %s (mp_native_qstr_table[%d])" % (q, i + 1), file=f) print("extern const uint16_t mp_native_qstr_table[];", file=f) - print("extern const mp_uint_t mp_native_obj_table[];", file=f) + print("extern const uintptr_t mp_native_obj_table[];", file=f) def do_link(args): @@ -1545,6 +1713,9 @@ def do_link(args): load_object_file(env, f, fn) if args.libs: + ar_util.init_cache( + f"{ar_util.DEFAULT_CACHE_BASE_PATH}-{args.arch}", ar_util.DEFAULT_CACHE_PREFIX + ) # Load archive info archives = [] for item in args.libs: @@ -1561,7 +1732,14 @@ def do_link(args): load_object_file(env, f, obj_name) link_objects(env, len(native_qstr_vals)) - build_mpy(env, args.output, native_qstr_vals, args.arch_flags) + if args.source_name: + internal_name = args.source_name + else: + import pathlib + + path = pathlib.Path(args.output) + internal_name = path.name + build_mpy(env, args.output, internal_name, native_qstr_vals, args.arch_flags) except LinkError as er: print("LinkError:", er.args[0]) sys.exit(1) @@ -1649,6 +1827,9 @@ def main(): ) cmd_parser.add_argument("--arch", default="x64", help="architecture") cmd_parser.add_argument("--arch-flags", default=None, help="optional architecture flags") + cmd_parser.add_argument( + "--source-name", default=None, help="override the file name written to the .mpy file" + ) cmd_parser.add_argument("--preprocess", action="store_true", help="preprocess source files") cmd_parser.add_argument("--qstrs", default=None, help="file defining additional qstrs") cmd_parser.add_argument( diff --git a/tools/pyboard.py b/tools/pyboard.py index c9f65d5d873..655e35a3a55 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -51,6 +51,7 @@ import ast import errno import os +import stat import struct import sys import time @@ -314,6 +315,23 @@ def __init__( if delayed: print("") + if device.startswith("execpty:"): + self.is_pty = True + else: + self.is_pty = self._is_pty_device(device) + + @staticmethod + def _is_pty_device(device): + """Detect if device is a PTY (pseudo-terminal), e.g. used by QEMU.""" + if device.startswith("/dev/pts/"): + try: + st = os.stat(device) + if stat.S_ISCHR(st.st_mode) and os.major(st.st_rdev) == 136: + return True + except (OSError, AttributeError): + pass + return False + def close(self): self.serial.close() @@ -339,8 +357,10 @@ def read_until( while True: if data.endswith(ending): break - elif self.serial.inWaiting() > 0: + new_data = None + if self.is_pty or self.serial.inWaiting() > 0: new_data = self.serial.read(1) + if new_data: if data_consumer: data_consumer(new_data) data = new_data diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index dba6ebd6de5..1818b7f89a4 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -38,7 +38,7 @@ def warning(self, text): def git_log(pretty_format, *args): # Delete pretty argument from user args so it doesn't interfere with what we do. - args = ["git", "log"] + [arg for arg in args if "--pretty" not in args] + args = ["git", "log"] + [arg for arg in args if "--pretty" not in arg] args.append("--pretty=format:" + pretty_format) very_verbose("git_log", *args) # Generator yielding each output line.